diff --git a/.env.example b/.env.example index b8983a0..e0480e6 100644 --- a/.env.example +++ b/.env.example @@ -9,20 +9,27 @@ PRODUCTION_APP_URL=https://polymarket-site-eta.vercel.app LIVE_TRADING_ENABLED=false # Accounts / authentication -AUTH_PROVIDER= +AUTH_PROVIDER=Clerk +CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= +CLERK_WEBHOOK_SIGNING_SECRET= DATABASE_URL= +NEON_DATABASE_URL= SESSION_SECRET= ACCOUNT_SESSION_SECRET= ENCRYPTION_KEY= PMA_ACCOUNTS_INDEX_PATH=accounts/index.json # Eligibility / compliance provider -KYC_PROVIDER= +KYC_PROVIDER=Veriff KYC_API_KEY= KYC_WEBHOOK_SECRET= -SANCTIONS_PROVIDER= +VERIFF_API_KEY= +VERIFF_SECRET_KEY= +VERIFF_WEBHOOK_SECRET= +SANCTIONS_PROVIDER=Veriff SANCTIONS_API_KEY= -GEOIP_PROVIDER= +GEOIP_PROVIDER=Veriff GEOIP_API_KEY= RESTRICTED_JURISDICTIONS=US,CA-NY,CA-ON TERMS_VERSION= @@ -31,16 +38,19 @@ RISK_DISCLOSURE_VERSION= MARKET_POLICY_STORE= # Wallet / deposit-wallet provider -WALLET_PROVIDER= +WALLET_PROVIDER=Circle WALLET_PROJECT_ID= WALLET_API_KEY= +CIRCLE_WALLET_SET_ID= DEPOSIT_WALLET_ADDRESS= POLYMARKET_SIGNATURE_TYPE=3 # Payments / deposits / withdrawals -PAYMENTS_PROVIDER= +PAYMENTS_PROVIDER=Circle PAYMENTS_API_KEY= PAYMENTS_WEBHOOK_SECRET= +CIRCLE_API_KEY= +CIRCLE_WEBHOOK_SECRET= WEBHOOK_BASE_URL= SETTLEMENT_ASSET=pUSD SETTLEMENT_CHAIN=polygon @@ -58,6 +68,11 @@ ACCOUNTING_EXPORT_STORE= RATE_LIMIT_STORE= AUDIT_LOG_STORE= MONITORING_DSN= +SENTRY_DSN= +SENTRY_AUTH_TOKEN= +SENTRY_ORG= +SENTRY_PROJECT= +SENTRY_WEBHOOK_URL= INCIDENT_WEBHOOK_URL= ADMIN_ALERT_EMAIL= CUSTOMER_SUPPORT_EMAIL= diff --git a/PROVIDER_SETUP.md b/PROVIDER_SETUP.md new file mode 100644 index 0000000..510faa0 --- /dev/null +++ b/PROVIDER_SETUP.md @@ -0,0 +1,87 @@ +# Provider Setup + +You signed up with `emplusbodyworks@gmail.com` for the selected launch stack: + +- Clerk for authentication. +- Neon for Postgres storage. +- Veriff for KYC / KYB / AML workflow. +- Circle for USDC, wallets, deposits, withdrawals, and webhooks. +- Sentry for monitoring. + +The app is wired for these providers, but private credentials must be copied +from each provider dashboard into Vercel environment variables. Never commit +real credentials to GitHub. + +## Vercel environment variables to add + +### Clerk + +- `AUTH_PROVIDER=Clerk` +- `CLERK_PUBLISHABLE_KEY` +- `CLERK_SECRET_KEY` +- `CLERK_WEBHOOK_SIGNING_SECRET` + +Clerk docs list `CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`, and +`CLERK_WEBHOOK_SIGNING_SECRET` for app connection and webhook verification. + +### Neon + +- `DATABASE_URL` +- `NEON_DATABASE_URL` optional alias; use the same value as `DATABASE_URL` + +Use the pooled Neon connection string when possible. Neon connection strings +look like `postgresql://...neon.tech/dbname?sslmode=require...`. + +### Veriff + +- `KYC_PROVIDER=Veriff` +- `SANCTIONS_PROVIDER=Veriff` +- `GEOIP_PROVIDER=Veriff` +- `VERIFF_API_KEY` +- `VERIFF_SECRET_KEY` +- `VERIFF_WEBHOOK_SECRET` + +The app treats Veriff as the selected KYC provider. Actual verification-session +creation is still locked until the final compliance flow is approved. + +### Circle + +- `PAYMENTS_PROVIDER=Circle` +- `WALLET_PROVIDER=Circle` +- `CIRCLE_API_KEY` +- `CIRCLE_WEBHOOK_SECRET` +- `CIRCLE_WALLET_SET_ID` + +Circle is wired as the selected payments/wallet provider. Live deposits, +withdrawals, and wallet execution remain disabled until compliance, wallet +signing, reconciliation, and testing are complete. + +### Sentry + +- `MONITORING_DSN` +- `SENTRY_DSN` +- `SENTRY_AUTH_TOKEN` +- `SENTRY_ORG` +- `SENTRY_PROJECT` +- `SENTRY_WEBHOOK_URL` + +`SENTRY_DSN` is exposed to the browser through `/api/config` so the frontend can +initialize Sentry. Keep auth tokens server-side only. + +## Already safe to set + +- `ADMIN_ALERT_EMAIL=emplusbodyworks@gmail.com` +- `CUSTOMER_SUPPORT_EMAIL=emplusbodyworks@gmail.com` +- `PRODUCTION_APP_URL=https://polymarket-site-eta.vercel.app` +- `RESTRICTED_JURISDICTIONS=US,CA-NY,CA-ON` +- `LIVE_TRADING_ENABLED=false` + +## What is still intentionally locked + +- Real deposits. +- Real withdrawals. +- Live Polymarket order placement. +- Automatic agent trading with real funds. + +Those should stay locked until legal review, eligibility checks, signed-order +execution, reconciliation, audit logs, and incident response are complete. diff --git a/README.md b/README.md index f122b98..a1ebfa5 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ Use `.env.example` as the setup template. - `ACCOUNT_SESSION_SECRET` signs cloud paper-account sessions. If omitted, the app falls back to the existing server secret/token, but production should use a dedicated value. +- `PROVIDER_SETUP.md` maps the current stack — Clerk, Neon, Veriff, Circle, and + Sentry — to the exact Vercel environment variables still needed. - `/api/live` reports whether KYC, payments, wallet/deposit-wallet, Polymarket CLOB, authentication, geofencing, sanctions, audit, support, and monitoring providers are configured. diff --git a/REAL_MONEY_ROADMAP.md b/REAL_MONEY_ROADMAP.md index 45d3ccd..111304e 100644 --- a/REAL_MONEY_ROADMAP.md +++ b/REAL_MONEY_ROADMAP.md @@ -11,6 +11,10 @@ interfaces visible without moving funds or placing orders. - `/api/live` accepts order intents only as dry-runs and returns an audit event. - `/api/accounts` creates, logs into, and saves password-backed paper accounts through Vercel Blob storage. +- `/api/config` exposes safe public provider configuration, including the + Sentry browser DSN and Clerk publishable key once configured. +- `/api/live` now checks the selected provider stack: Clerk, Neon, Veriff, + Circle, and Sentry. - The Live Money tab shows launch checks, wallet/deposit settings, agent risk limits, a dry-run order console, and an audit preview. diff --git a/api/config.js b/api/config.js new file mode 100644 index 0000000..bf4b363 --- /dev/null +++ b/api/config.js @@ -0,0 +1,23 @@ +export default async function handler(req, res) { + res.setHeader("Cache-Control", "no-store"); + if (req.method !== "GET") { + res.setHeader("Allow", "GET"); + return res.status(405).json({ ok: false, error: "Method not allowed" }); + } + + return res.status(200).json({ + ok: true, + providers: { + auth: process.env.AUTH_PROVIDER || "Clerk", + database: process.env.DATABASE_PROVIDER || "Neon", + kyc: process.env.KYC_PROVIDER || "Veriff", + payments: process.env.PAYMENTS_PROVIDER || "Circle", + wallet: process.env.WALLET_PROVIDER || "Circle", + monitoring: process.env.MONITORING_PROVIDER || "Sentry", + }, + public: { + clerk_publishable_key: process.env.CLERK_PUBLISHABLE_KEY || "", + sentry_dsn: process.env.SENTRY_DSN || process.env.MONITORING_DSN || "", + }, + }); +} diff --git a/api/live.js b/api/live.js index 2610ac7..dee0ae6 100644 --- a/api/live.js +++ b/api/live.js @@ -40,6 +40,57 @@ const REQUIRED_ENV = [ ["CUSTOMER_SUPPORT_EMAIL", "Customer support contact"], ]; +const ENV_ALIASES = { + DATABASE_URL: ["NEON_DATABASE_URL"], + KYC_API_KEY: ["VERIFF_API_KEY"], + KYC_WEBHOOK_SECRET: ["VERIFF_WEBHOOK_SECRET", "VERIFF_SECRET_KEY"], + SANCTIONS_API_KEY: ["VERIFF_API_KEY"], + GEOIP_API_KEY: ["VERIFF_API_KEY"], + PAYMENTS_API_KEY: ["CIRCLE_API_KEY"], + PAYMENTS_WEBHOOK_SECRET: ["CIRCLE_WEBHOOK_SECRET"], + WALLET_PROJECT_ID: ["CIRCLE_WALLET_SET_ID"], + WALLET_API_KEY: ["CIRCLE_API_KEY"], + RECONCILIATION_STORE: ["DATABASE_URL", "NEON_DATABASE_URL"], + ACCOUNTING_EXPORT_STORE: ["DATABASE_URL", "NEON_DATABASE_URL"], + RATE_LIMIT_STORE: ["DATABASE_URL", "NEON_DATABASE_URL"], + AUDIT_LOG_STORE: ["DATABASE_URL", "NEON_DATABASE_URL"], + MONITORING_DSN: ["SENTRY_DSN"], + INCIDENT_WEBHOOK_URL: ["SENTRY_WEBHOOK_URL"], +}; + +const PROVIDER_STACK = [ + { + provider: "Clerk", + role: "User auth, sessions, sign-in/sign-out, account identity", + env: ["AUTH_PROVIDER", "CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY", "CLERK_WEBHOOK_SIGNING_SECRET"], + dashboard: "https://dashboard.clerk.com/", + }, + { + provider: "Neon", + role: "Postgres database for users, portfolios, audit metadata, reconciliation, and history", + env: ["DATABASE_URL"], + dashboard: "https://console.neon.tech/", + }, + { + provider: "Veriff", + role: "KYC/KYB, identity verification, age checks, AML/sanctions workflow", + env: ["KYC_PROVIDER", "VERIFF_API_KEY", "VERIFF_SECRET_KEY", "VERIFF_WEBHOOK_SECRET"], + dashboard: "https://station.veriff.com/", + }, + { + provider: "Circle", + role: "USDC, embedded wallets, payments/deposits/withdrawals, webhooks", + env: ["PAYMENTS_PROVIDER", "WALLET_PROVIDER", "CIRCLE_API_KEY", "CIRCLE_WEBHOOK_SECRET", "CIRCLE_WALLET_SET_ID"], + dashboard: "https://console.circle.com/", + }, + { + provider: "Sentry", + role: "Error monitoring, browser/backend issue tracking, incident visibility", + env: ["MONITORING_DSN", "SENTRY_DSN"], + dashboard: "https://sentry.io/", + }, +]; + const LAUNCH_REQUIREMENTS = [ ["legal", "Terms, privacy policy, risk disclosures, and jurisdiction policy approved"], ["eligibility", "KYC/KYB, age, sanctions, watchlist, and location screening active"], @@ -56,15 +107,32 @@ const LAUNCH_REQUIREMENTS = [ ["testing", "Paper-to-live parity, dry-runs, test wallets, webhook tests, and edge-case QA completed"], ]; +function envValue(key) { + if (process.env[key]) return process.env[key]; + return (ENV_ALIASES[key] || []).map((alias) => process.env[alias]).find(Boolean); +} + function providerStatus() { return REQUIRED_ENV.map(([key, label]) => ({ key, label, - configured: Boolean(process.env[key]), + configured: Boolean(envValue(key)), + aliases: ENV_ALIASES[key] || [], expected: key === "POLYMARKET_SIGNATURE_TYPE" ? "3 for new deposit-wallet API users" : undefined, })); } +function stackStatus() { + return PROVIDER_STACK.map((provider) => { + const checks = provider.env.map((key) => ({ key, configured: Boolean(envValue(key)) })); + return { + ...provider, + configured: checks.every((x) => x.configured), + checks, + }; + }); +} + function liveTradingReady() { const requiredConfigured = providerStatus().every((x) => x.configured); const liveFlagEnabled = process.env.LIVE_TRADING_ENABLED === "true"; @@ -92,6 +160,7 @@ function baseStatus() { ? null : "Live trading is locked until eligibility, payments, wallet/deposit-wallet signing, Polymarket CLOB credentials, audit storage, monitoring, and LIVE_TRADING_ENABLED=true are configured.", providers, + provider_stack: stackStatus(), launch_requirements: LAUNCH_REQUIREMENTS.map(([key, label]) => ({ key, label })), next_required: missing, docs: { diff --git a/index.html b/index.html index 617f527..f44d4e5 100644 --- a/index.html +++ b/index.html @@ -683,6 +683,7 @@ let PAPER_SEARCH_RESULTS = null; let PAPER_MARKET_OFFSET = 0; let PAPER_MARKET_QUERY = ""; let LIVE_BACKEND_STATUS = null; +let PROVIDER_CONFIG = null; const catColor = (c) => CAT_COLORS[c] || CAT_COLORS.Other; const getFocus = () => localStorage.getItem(FOCUS_KEY) || "All"; const setFocus = (c) => localStorage.setItem(FOCUS_KEY, c); @@ -702,6 +703,21 @@ function applyPersonalMode(){ }); } +async function initProviderConfig(){ + try{ + const r=await fetch("/api/config",{cache:"no-store"}); + PROVIDER_CONFIG=await r.json(); + const dsn=PROVIDER_CONFIG&&PROVIDER_CONFIG.public&&PROVIDER_CONFIG.public.sentry_dsn; + if(dsn&&!window.Sentry){ + const script=document.createElement("script"); + script.src="https://browser.sentry-cdn.com/8.55.0/bundle.tracing.min.js"; + script.crossOrigin="anonymous"; + script.onload=()=>{if(window.Sentry)window.Sentry.init({dsn,tracesSampleRate:0.1,environment:"production"});}; + document.head.appendChild(script); + } + }catch(e){} +} + /* ---------- the ten competing agents ---------- Each shares the same hourly suggestions but ranks/sizes them differently. */ const AGENTS = [ @@ -2339,9 +2355,10 @@ function renderLiveBackendStatus(){ const s=LIVE_BACKEND_STATUS; if(!s){root.innerHTML="Checking backend readiness...";return;} const providers=(s.providers||[]).map(p=>`
${esc(p.label)}${p.expected?`
${esc(p.expected)}
`:""}
${p.configured?"configured":"missing"}
`).join(""); + const stack=(s.provider_stack||[]).map(p=>`
${esc(p.provider)}
${esc(p.role)}
${(p.checks||[]).map(c=>`${esc(c.key)}: ${c.configured?"ok":"missing"}`).join(" · ")}
${p.configured?"ready":"needs keys"}
`).join(""); const next=(s.next_required||[]).slice(0,12).map(x=>`
  • ${esc(x)}
  • `).join(""); const reqs=(s.launch_requirements||[]).map(x=>`
  • ${esc(x.label)}
  • `).join(""); - root.innerHTML=`${s.live_trading_enabled?"Live trading unlocked":"Live trading locked"}
    ${esc(s.locked_reason||"All required backend providers are configured.")}
    ${providers}${next?`
    Next setup items
    `:""}${reqs?`
    Full launch requirements
      ${reqs}
    `:""}
    Live execution stays disabled until every provider is configured, every launch requirement is complete, and final approval flips LIVE_TRADING_ENABLED=true.
    `; + root.innerHTML=`${s.live_trading_enabled?"Live trading unlocked":"Live trading locked"}
    ${esc(s.locked_reason||"All required backend providers are configured.")}
    ${stack?`
    Your provider stack${stack}
    `:""}${providers}${next?`
    Next setup items
    `:""}${reqs?`
    Full launch requirements
      ${reqs}
    `:""}
    Live execution stays disabled until every provider is configured, every launch requirement is complete, and final approval flips LIVE_TRADING_ENABLED=true.
    `; } async function dryRunLiveOrderIntent(){ const s=loadLiveState(); @@ -2411,6 +2428,7 @@ window.addEventListener("hashchange",()=>showTab(location.hash.slice(1))); /* ---------- Wire up ---------- */ applyPersonalMode(); +initProviderConfig(); $("runBtn").addEventListener("click",async()=>{ const btn=$("runBtn");btn.disabled=true;btn.textContent="Running…"; try{const r=await runDailyCycle();const b=board();toast(r.skipped?`Already ran this hour · ${b[0].c.emoji} ${b[0].c.name} leads`:`Cycle done · ${r.suggestions} ideas · ${b[0].c.emoji} ${b[0].c.name} leads`);renderAll();}