Add selected provider stack setup

This commit is contained in:
Theodore Song
2026-07-12 22:34:01 -04:00
parent 7cafa179cb
commit f89cadbb40
7 changed files with 226 additions and 8 deletions
+21 -6
View File
@@ -9,20 +9,27 @@ PRODUCTION_APP_URL=https://polymarket-site-eta.vercel.app
LIVE_TRADING_ENABLED=false LIVE_TRADING_ENABLED=false
# Accounts / authentication # Accounts / authentication
AUTH_PROVIDER= AUTH_PROVIDER=Clerk
CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SIGNING_SECRET=
DATABASE_URL= DATABASE_URL=
NEON_DATABASE_URL=
SESSION_SECRET= SESSION_SECRET=
ACCOUNT_SESSION_SECRET= ACCOUNT_SESSION_SECRET=
ENCRYPTION_KEY= ENCRYPTION_KEY=
PMA_ACCOUNTS_INDEX_PATH=accounts/index.json PMA_ACCOUNTS_INDEX_PATH=accounts/index.json
# Eligibility / compliance provider # Eligibility / compliance provider
KYC_PROVIDER= KYC_PROVIDER=Veriff
KYC_API_KEY= KYC_API_KEY=
KYC_WEBHOOK_SECRET= KYC_WEBHOOK_SECRET=
SANCTIONS_PROVIDER= VERIFF_API_KEY=
VERIFF_SECRET_KEY=
VERIFF_WEBHOOK_SECRET=
SANCTIONS_PROVIDER=Veriff
SANCTIONS_API_KEY= SANCTIONS_API_KEY=
GEOIP_PROVIDER= GEOIP_PROVIDER=Veriff
GEOIP_API_KEY= GEOIP_API_KEY=
RESTRICTED_JURISDICTIONS=US,CA-NY,CA-ON RESTRICTED_JURISDICTIONS=US,CA-NY,CA-ON
TERMS_VERSION= TERMS_VERSION=
@@ -31,16 +38,19 @@ RISK_DISCLOSURE_VERSION=
MARKET_POLICY_STORE= MARKET_POLICY_STORE=
# Wallet / deposit-wallet provider # Wallet / deposit-wallet provider
WALLET_PROVIDER= WALLET_PROVIDER=Circle
WALLET_PROJECT_ID= WALLET_PROJECT_ID=
WALLET_API_KEY= WALLET_API_KEY=
CIRCLE_WALLET_SET_ID=
DEPOSIT_WALLET_ADDRESS= DEPOSIT_WALLET_ADDRESS=
POLYMARKET_SIGNATURE_TYPE=3 POLYMARKET_SIGNATURE_TYPE=3
# Payments / deposits / withdrawals # Payments / deposits / withdrawals
PAYMENTS_PROVIDER= PAYMENTS_PROVIDER=Circle
PAYMENTS_API_KEY= PAYMENTS_API_KEY=
PAYMENTS_WEBHOOK_SECRET= PAYMENTS_WEBHOOK_SECRET=
CIRCLE_API_KEY=
CIRCLE_WEBHOOK_SECRET=
WEBHOOK_BASE_URL= WEBHOOK_BASE_URL=
SETTLEMENT_ASSET=pUSD SETTLEMENT_ASSET=pUSD
SETTLEMENT_CHAIN=polygon SETTLEMENT_CHAIN=polygon
@@ -58,6 +68,11 @@ ACCOUNTING_EXPORT_STORE=
RATE_LIMIT_STORE= RATE_LIMIT_STORE=
AUDIT_LOG_STORE= AUDIT_LOG_STORE=
MONITORING_DSN= MONITORING_DSN=
SENTRY_DSN=
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_WEBHOOK_URL=
INCIDENT_WEBHOOK_URL= INCIDENT_WEBHOOK_URL=
ADMIN_ALERT_EMAIL= ADMIN_ALERT_EMAIL=
CUSTOMER_SUPPORT_EMAIL= CUSTOMER_SUPPORT_EMAIL=
+87
View File
@@ -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.
+2
View File
@@ -47,6 +47,8 @@ Use `.env.example` as the setup template.
- `ACCOUNT_SESSION_SECRET` signs cloud paper-account sessions. If omitted, the - `ACCOUNT_SESSION_SECRET` signs cloud paper-account sessions. If omitted, the
app falls back to the existing server secret/token, but production should use app falls back to the existing server secret/token, but production should use
a dedicated value. 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 - `/api/live` reports whether KYC, payments, wallet/deposit-wallet, Polymarket
CLOB, authentication, geofencing, sanctions, audit, support, and monitoring CLOB, authentication, geofencing, sanctions, audit, support, and monitoring
providers are configured. providers are configured.
+4
View File
@@ -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/live` accepts order intents only as dry-runs and returns an audit event.
- `/api/accounts` creates, logs into, and saves password-backed paper accounts - `/api/accounts` creates, logs into, and saves password-backed paper accounts
through Vercel Blob storage. 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 - The Live Money tab shows launch checks, wallet/deposit settings, agent risk
limits, a dry-run order console, and an audit preview. limits, a dry-run order console, and an audit preview.
+23
View File
@@ -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 || "",
},
});
}
+70 -1
View File
@@ -40,6 +40,57 @@ const REQUIRED_ENV = [
["CUSTOMER_SUPPORT_EMAIL", "Customer support contact"], ["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 = [ const LAUNCH_REQUIREMENTS = [
["legal", "Terms, privacy policy, risk disclosures, and jurisdiction policy approved"], ["legal", "Terms, privacy policy, risk disclosures, and jurisdiction policy approved"],
["eligibility", "KYC/KYB, age, sanctions, watchlist, and location screening active"], ["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"], ["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() { function providerStatus() {
return REQUIRED_ENV.map(([key, label]) => ({ return REQUIRED_ENV.map(([key, label]) => ({
key, key,
label, 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, 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() { function liveTradingReady() {
const requiredConfigured = providerStatus().every((x) => x.configured); const requiredConfigured = providerStatus().every((x) => x.configured);
const liveFlagEnabled = process.env.LIVE_TRADING_ENABLED === "true"; const liveFlagEnabled = process.env.LIVE_TRADING_ENABLED === "true";
@@ -92,6 +160,7 @@ function baseStatus() {
? null ? 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.", : "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, providers,
provider_stack: stackStatus(),
launch_requirements: LAUNCH_REQUIREMENTS.map(([key, label]) => ({ key, label })), launch_requirements: LAUNCH_REQUIREMENTS.map(([key, label]) => ({ key, label })),
next_required: missing, next_required: missing,
docs: { docs: {
+19 -1
View File
@@ -683,6 +683,7 @@ let PAPER_SEARCH_RESULTS = null;
let PAPER_MARKET_OFFSET = 0; let PAPER_MARKET_OFFSET = 0;
let PAPER_MARKET_QUERY = ""; let PAPER_MARKET_QUERY = "";
let LIVE_BACKEND_STATUS = null; let LIVE_BACKEND_STATUS = null;
let PROVIDER_CONFIG = null;
const catColor = (c) => CAT_COLORS[c] || CAT_COLORS.Other; const catColor = (c) => CAT_COLORS[c] || CAT_COLORS.Other;
const getFocus = () => localStorage.getItem(FOCUS_KEY) || "All"; const getFocus = () => localStorage.getItem(FOCUS_KEY) || "All";
const setFocus = (c) => localStorage.setItem(FOCUS_KEY, c); 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 ---------- /* ---------- the ten competing agents ----------
Each shares the same hourly suggestions but ranks/sizes them differently. */ Each shares the same hourly suggestions but ranks/sizes them differently. */
const AGENTS = [ const AGENTS = [
@@ -2339,9 +2355,10 @@ function renderLiveBackendStatus(){
const s=LIVE_BACKEND_STATUS; const s=LIVE_BACKEND_STATUS;
if(!s){root.innerHTML="Checking backend readiness...";return;} if(!s){root.innerHTML="Checking backend readiness...";return;}
const providers=(s.providers||[]).map(p=>`<div class="leader-row"><span>${esc(p.label)}${p.expected?`<div class="small muted">${esc(p.expected)}</div>`:""}</span><b class="${p.configured?"pos-val":"neg-val"}">${p.configured?"configured":"missing"}</b></div>`).join(""); const providers=(s.providers||[]).map(p=>`<div class="leader-row"><span>${esc(p.label)}${p.expected?`<div class="small muted">${esc(p.expected)}</div>`:""}</span><b class="${p.configured?"pos-val":"neg-val"}">${p.configured?"configured":"missing"}</b></div>`).join("");
const stack=(s.provider_stack||[]).map(p=>`<div class="leader-row"><span><b>${esc(p.provider)}</b><div class="small muted">${esc(p.role)}</div><div class="small muted">${(p.checks||[]).map(c=>`${esc(c.key)}: ${c.configured?"ok":"missing"}`).join(" · ")}</div></span><b class="${p.configured?"pos-val":"neg-val"}">${p.configured?"ready":"needs keys"}</b></div>`).join("");
const next=(s.next_required||[]).slice(0,12).map(x=>`<li>${esc(x)}</li>`).join(""); const next=(s.next_required||[]).slice(0,12).map(x=>`<li>${esc(x)}</li>`).join("");
const reqs=(s.launch_requirements||[]).map(x=>`<li>${esc(x.label)}</li>`).join(""); const reqs=(s.launch_requirements||[]).map(x=>`<li>${esc(x.label)}</li>`).join("");
root.innerHTML=`<b>${s.live_trading_enabled?"Live trading unlocked":"Live trading locked"}</b><div class="small muted" style="margin:6px 0">${esc(s.locked_reason||"All required backend providers are configured.")}</div>${providers}${next?`<div class="locked-panel" style="margin-top:10px"><b>Next setup items</b><ul>${next}</ul></div>`:""}${reqs?`<div class="locked-panel" style="margin-top:10px"><b>Full launch requirements</b><ol>${reqs}</ol></div>`:""}<div class="small muted" style="margin-top:8px">Live execution stays disabled until every provider is configured, every launch requirement is complete, and final approval flips LIVE_TRADING_ENABLED=true.</div>`; root.innerHTML=`<b>${s.live_trading_enabled?"Live trading unlocked":"Live trading locked"}</b><div class="small muted" style="margin:6px 0">${esc(s.locked_reason||"All required backend providers are configured.")}</div>${stack?`<div class="locked-panel" style="margin-top:10px"><b>Your provider stack</b>${stack}</div>`:""}${providers}${next?`<div class="locked-panel" style="margin-top:10px"><b>Next setup items</b><ul>${next}</ul></div>`:""}${reqs?`<div class="locked-panel" style="margin-top:10px"><b>Full launch requirements</b><ol>${reqs}</ol></div>`:""}<div class="small muted" style="margin-top:8px">Live execution stays disabled until every provider is configured, every launch requirement is complete, and final approval flips LIVE_TRADING_ENABLED=true.</div>`;
} }
async function dryRunLiveOrderIntent(){ async function dryRunLiveOrderIntent(){
const s=loadLiveState(); const s=loadLiveState();
@@ -2411,6 +2428,7 @@ window.addEventListener("hashchange",()=>showTab(location.hash.slice(1)));
/* ---------- Wire up ---------- */ /* ---------- Wire up ---------- */
applyPersonalMode(); applyPersonalMode();
initProviderConfig();
$("runBtn").addEventListener("click",async()=>{ $("runBtn").addEventListener("click",async()=>{
const btn=$("runBtn");btn.disabled=true;btn.textContent="Running…"; 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();} 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();}