import { WEEKLY_COVENANT_SECONDS, buildCookie, createSessionToken, getSoulwallStore, json } from './_session.js'; import { getDonationIntentByExternalId, getDonationIntentById, getSupabaseUser, markDonationIntentSucceeded, upsertLedgerEvent, upsertSoulwallSession } from './_supabase.js'; import { getGivethProject, normalizeTxHash, verifyGivethDonation } from './_giveth.js'; async function grantGivethAccess(env, { intent, txHash, email, userId }) { if (intent?.status === 'succeeded' && intent?.access_expires_at) { return { duplicate: true, accessExpiresAt: intent.access_expires_at }; } const accessExpiresAt = new Date(Date.now() + WEEKLY_COVENANT_SECONDS * 1000).toISOString(); const profile = await upsertSoulwallSession(env, { email, userId, expiresAt: accessExpiresAt, source: 'giveth', transactionId: txHash }); if (!profile) return { error: 'supabase_profile_update_failed' }; await markDonationIntentSucceeded(env, { uniqueIntentId: intent.unique_intent_id, transactionId: txHash, accessExpiresAt, provider: 'giveth', externalId: txHash }); return { accessExpiresAt }; } async function grantAnonymousGivethAccess(env, txHash) { const store = getSoulwallStore(env); const nowMs = Date.now(); let accessExpiresAt = new Date(nowMs + WEEKLY_COVENANT_SECONDS * 1000).toISOString(); let restored = false; if (store) { const consumedKey = `giveth_tx:${txHash}`; const consumed = await store.get(consumedKey); if (consumed) { try { const parsed = JSON.parse(consumed); const storedExpiresAt = String(parsed?.accessExpiresAt || ''); const expiresAtMs = Date.parse(storedExpiresAt); if (Number.isFinite(expiresAtMs) && expiresAtMs > nowMs) { accessExpiresAt = new Date(expiresAtMs).toISOString(); restored = true; } } catch (error) { // Ignore bad store payloads and mint a fresh anonymous session below. } } if (!restored) { await store.put(consumedKey, JSON.stringify({ claimedAt: new Date(nowMs).toISOString(), accessExpiresAt }), { expirationTtl: WEEKLY_COVENANT_SECONDS }); } } const expSeconds = Math.floor(Date.parse(accessExpiresAt) / 1000); const session = await createSessionToken(env, restored ? 'giveth-restore' : 'giveth', expSeconds); if (!session?.token) { return { error: 'session_token_unavailable', message: 'SoulWall could not mint a browser session token.' }; } return { accessExpiresAt, restored, cookie: buildCookie(session.token, Math.max(1, Math.floor((Date.parse(accessExpiresAt) - nowMs) / 1000))) }; } async function recordGivethLedgerEvent(env, request, { txHash, projectSlug = '', projectName = '', amountUsd = null, amountPol = null, accessExpiresAt = '', clientId = '', deploymentTitle = '', deploymentDescription = '', sourceWebsite = '', integrationOrigin = '', rawEvent = null }) { try { return await upsertLedgerEvent(env, { eventId: txHash, txHash, transactionId: txHash, request, clientId, integrationOrigin, sourceWebsite, deploymentTitle, deploymentDescription, provider: 'giveth', projectSlug, projectName, currency: amountPol ? 'POL' : 'USD', amountUsd, amountNative: amountPol, network: 'polygon', accessExpiresAt, rawEvent: { source: 'soulwall-giveth-verify', proofRecognized: true, txHash, ...(rawEvent && typeof rawEvent === 'object' ? rawEvent : {}) } }); } catch (error) { return null; } } export async function onRequestPost({ env, request }) { let body = {}; try { body = await request.json(); } catch (error) { return json({ error: 'invalid_json' }, 400); } const txHash = normalizeTxHash(body.txHash || body.transactionHash || ''); const projectSlug = String(body.projectSlug || body.nonprofitSlug || '').trim(); const intentId = String(body.partnerDonationId || body.intentId || '').trim(); if (!txHash || !projectSlug) { return json({ error: 'missing_required_fields', required: ['txHash', 'projectSlug'], received: { txHash: Boolean(txHash), projectSlug: Boolean(projectSlug), partnerDonationId: Boolean(intentId) } }, 400); } const user = await getSupabaseUser(env, request); const existingExternal = await getDonationIntentByExternalId(env, txHash); if (existingExternal?.status === 'succeeded' && existingExternal?.access_expires_at) { const existingProjectSlug = existingExternal.nonprofit_slug || projectSlug; const existingProject = existingProjectSlug ? getGivethProject(env, existingProjectSlug) : null; const refreshedProof = existingProjectSlug ? await verifyGivethDonation(env, { txHash, projectSlug: existingProjectSlug }) : null; const refreshedProject = refreshedProof?.ok ? refreshedProof.project : existingProject; await recordGivethLedgerEvent(env, request, { txHash, projectSlug: refreshedProject?.slug || existingProjectSlug, projectName: refreshedProject?.name || '', amountUsd: refreshedProof?.ok ? refreshedProof.amountUsd : null, amountPol: refreshedProof?.ok ? refreshedProof.amountPol : null, accessExpiresAt: existingExternal.access_expires_at, clientId: body.clientId || body.client_id || '', deploymentTitle: body.deploymentTitle || body.title || body.accessTitle || '', deploymentDescription: body.deploymentDescription || body.description || body.accessDescription || '', sourceWebsite: body.sourceWebsite || body.website || body.donationSourceWebsite || '', integrationOrigin: body.integrationOrigin || body.origin || '', rawEvent: { duplicate: true, donationIntentId: existingExternal.unique_intent_id || '' } }); const expiresAtMs = Date.parse(existingExternal.access_expires_at); const nowMs = Date.now(); const stillOpen = Number.isFinite(expiresAtMs) && expiresAtMs > nowMs; if (stillOpen) { const expSeconds = Math.floor(expiresAtMs / 1000); const session = await createSessionToken(env, 'giveth-duplicate', expSeconds); const headers = session?.token ? { 'set-cookie': buildCookie(session.token, Math.max(1, Math.floor((expiresAtMs - nowMs) / 1000))) } : {}; return json({ accepted: true, duplicate: true, unlocked: true, provider: 'giveth', message: 'This confirmed Giveth transaction already opened SoulWall. Restoring the active session.', transactionId: txHash, accessExpiresAt: existingExternal.access_expires_at }, 200, headers); } return json({ accepted: true, duplicate: true, unlocked: false, provider: 'giveth', error: 'giveth_tx_already_claimed', message: 'This Giveth transaction was already used and its SoulWall window has expired.', transactionId: txHash, accessExpiresAt: existingExternal.access_expires_at }, 409); } let intent = null; if (user?.email && user?.id && intentId) { intent = await getDonationIntentById(env, intentId); if (!intent?.unique_intent_id || intent.user_id !== user.id || intent.email !== user.email) { return json({ error: 'giveth_intent_not_found' }, 404); } } const verified = await verifyGivethDonation(env, { txHash, projectSlug }); if (!verified.ok) { return json({ accepted: true, unlocked: false, provider: 'giveth', error: verified.error, message: verified.message || 'Giveth donation is not confirmed yet.', details: verified.details || [] }, 202); } await recordGivethLedgerEvent(env, request, { txHash, projectSlug: verified.project?.slug || projectSlug, projectName: verified.project?.name || '', amountUsd: verified.amountUsd, amountPol: verified.amountPol, clientId: body.clientId || body.client_id || '', deploymentTitle: body.deploymentTitle || body.title || body.accessTitle || '', deploymentDescription: body.deploymentDescription || body.description || body.accessDescription || '', sourceWebsite: body.sourceWebsite || body.website || body.donationSourceWebsite || '', integrationOrigin: body.integrationOrigin || body.origin || '', rawEvent: { donationIntentId: intent?.unique_intent_id || '', authenticated: Boolean(user?.id && intent?.unique_intent_id) } }); if (!user?.email || !user?.id || !intent?.unique_intent_id) { const anonymousResult = await grantAnonymousGivethAccess(env, txHash); if (anonymousResult.error) { return json({ error: anonymousResult.error, message: anonymousResult.message }, anonymousResult.error === 'auth_required' ? 401 : 409); } return json({ accepted: true, unlocked: true, restored: Boolean(anonymousResult.restored), provider: 'giveth', projectSlug: verified.project?.slug || projectSlug, projectName: verified.project?.name || '', amountPol: verified.amountPol, amountUsd: verified.amountUsd, transactionId: txHash, accessExpiresAt: anonymousResult.accessExpiresAt }, 200, { 'set-cookie': anonymousResult.cookie }); } const result = await grantGivethAccess(env, { intent, txHash, email: user.email, userId: user.id }); if (result.error) return json({ error: result.error }, 503); return json({ accepted: true, unlocked: !result.duplicate, duplicate: Boolean(result.duplicate), provider: 'giveth', projectSlug: verified.project?.slug || projectSlug, projectName: verified.project?.name || '', amountPol: verified.amountPol, amountUsd: verified.amountUsd, transactionId: txHash, accessExpiresAt: result.accessExpiresAt }); }