2026-07-13 16:47:06 -04:00
import { recordAuditEvent } from "./_db.js" ;
2026-07-12 16:50:03 -04:00
const REQUIRED_ENV = [
2026-07-12 17:07:07 -04:00
[ "PRODUCTION_APP_URL" , "Production app URL" ],
[ "AUTH_PROVIDER" , "Real account authentication provider" ],
[ "DATABASE_URL" , "Production account database" ],
[ "SESSION_SECRET" , "Session signing secret" ],
[ "ENCRYPTION_KEY" , "Encryption key for sensitive user settings" ],
2026-07-12 16:50:03 -04:00
[ "KYC_PROVIDER" , "KYC / eligibility provider" ],
2026-07-12 16:58:10 -04:00
[ "KYC_API_KEY" , "KYC provider API key" ],
[ "KYC_WEBHOOK_SECRET" , "KYC webhook secret" ],
2026-07-12 17:07:07 -04:00
[ "SANCTIONS_PROVIDER" , "Sanctions / watchlist screening provider" ],
[ "SANCTIONS_API_KEY" , "Sanctions provider API key" ],
[ "GEOIP_PROVIDER" , "Geo-IP / geofencing provider" ],
[ "GEOIP_API_KEY" , "Geo-IP provider API key" ],
[ "RESTRICTED_JURISDICTIONS" , "Restricted jurisdiction rules" ],
[ "TERMS_VERSION" , "Approved terms version" ],
[ "PRIVACY_VERSION" , "Approved privacy policy version" ],
[ "RISK_DISCLOSURE_VERSION" , "Approved risk disclosure version" ],
[ "MARKET_POLICY_STORE" , "Market eligibility and category policy store" ],
2026-07-13 16:47:06 -04:00
[ "CONSENT_STORE" , "Versioned user consent store" ],
2026-07-12 16:50:03 -04:00
[ "PAYMENTS_PROVIDER" , "Deposit and withdrawal provider" ],
2026-07-12 16:58:10 -04:00
[ "PAYMENTS_API_KEY" , "Payments provider API key" ],
[ "PAYMENTS_WEBHOOK_SECRET" , "Payments webhook secret" ],
2026-07-12 17:07:07 -04:00
[ "WEBHOOK_BASE_URL" , "Public webhook base URL" ],
2026-07-12 16:50:03 -04:00
[ "WALLET_PROVIDER" , "Wallet or deposit-wallet provider" ],
2026-07-12 16:58:10 -04:00
[ "WALLET_PROJECT_ID" , "Wallet provider project ID" ],
[ "WALLET_API_KEY" , "Wallet provider API key" ],
[ "DEPOSIT_WALLET_ADDRESS" , "Polymarket deposit-wallet address" ],
[ "POLYMARKET_SIGNATURE_TYPE" , "Polymarket signature type" ],
2026-07-12 16:50:03 -04:00
[ "POLYMARKET_CLOB_API_KEY" , "Polymarket CLOB API key" ],
[ "POLYMARKET_CLOB_SECRET" , "Polymarket CLOB API secret" ],
[ "POLYMARKET_CLOB_PASSPHRASE" , "Polymarket CLOB passphrase" ],
2026-07-12 16:58:10 -04:00
[ "SETTLEMENT_ASSET" , "Settlement asset" ],
[ "SETTLEMENT_CHAIN" , "Settlement chain" ],
2026-07-12 17:07:07 -04:00
[ "RECONCILIATION_STORE" , "Balance, position, and fill reconciliation store" ],
[ "ACCOUNTING_EXPORT_STORE" , "Statements, tax, and accounting export store" ],
[ "RATE_LIMIT_STORE" , "Rate limit and abuse prevention store" ],
2026-07-12 16:50:03 -04:00
[ "AUDIT_LOG_STORE" , "Durable audit log store" ],
2026-07-12 17:07:07 -04:00
[ "MONITORING_DSN" , "Error and performance monitoring" ],
[ "INCIDENT_WEBHOOK_URL" , "Incident alert webhook" ],
2026-07-13 16:47:06 -04:00
[ "RISK_ADMIN_TOKEN" , "Risk admin incident-control token" ],
2026-07-12 16:58:10 -04:00
[ "ADMIN_ALERT_EMAIL" , "Admin alert destination" ],
2026-07-12 17:07:07 -04:00
[ "CUSTOMER_SUPPORT_EMAIL" , "Customer support contact" ],
];
2026-07-14 07:54:50 -04:00
const PERSONAL_REQUIRED_ENV = [
[ "DATABASE_URL" , "Audit database" ],
[ "DEPOSIT_WALLET_ADDRESS" , "Personal Polymarket deposit wallet address" ],
[ "POLYMARKET_SIGNATURE_TYPE" , "Polymarket signature type" ],
[ "POLYMARKET_CLOB_API_KEY" , "Personal Polymarket CLOB API key" ],
[ "POLYMARKET_CLOB_SECRET" , "Personal Polymarket CLOB API secret" ],
[ "POLYMARKET_CLOB_PASSPHRASE" , "Personal Polymarket CLOB passphrase" ],
[ "RISK_ADMIN_TOKEN" , "Personal admin token" ],
];
2026-07-12 22:34:01 -04:00
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" ],
2026-07-13 16:47:06 -04:00
CONSENT_STORE : [ "DATABASE_URL" , "NEON_DATABASE_URL" ],
2026-07-12 22:34:01 -04:00
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/" ,
},
];
2026-07-12 17:07:07 -04:00
const LAUNCH_REQUIREMENTS = [
[ "legal" , "Terms, privacy policy, risk disclosures, and jurisdiction policy approved" ],
[ "eligibility" , "KYC/KYB, age, sanctions, watchlist, and location screening active" ],
[ "market_policy" , "Allowed market categories, restricted events, and manipulation rules defined" ],
[ "auth" , "Production accounts, sessions, password reset, MFA path, and sign-out implemented" ],
[ "wallets" , "User-controlled wallet/deposit-wallet signing and permission revocation implemented" ],
[ "payments" , "Deposits, withdrawals, webhooks, failed-payment handling, and support flows implemented" ],
[ "execution" , "Signed order creation, CLOB submission, cancellation, retry, and fill tracking implemented" ],
[ "reconciliation" , "Cash, open orders, positions, fills, fees, and withdrawals reconciled continuously" ],
[ "risk" , "Agent allocation caps, market caps, stop rules, manual approval, and emergency pause enforced server-side" ],
[ "security" , "Secret management, encryption, rate limits, abuse controls, and security review completed" ],
[ "records" , "Append-only audit logs, customer statements, exports, and retention policy active" ],
[ "operations" , "Monitoring, incident response, customer support, and rollback plan ready" ],
[ "testing" , "Paper-to-live parity, dry-runs, test wallets, webhook tests, and edge-case QA completed" ],
2026-07-12 16:50:03 -04:00
];
2026-07-13 09:16:50 -04:00
const WEBHOOK_ROUTES = [
[ "clerk" , "Clerk user and session events" , "/api/webhooks/clerk" ],
[ "veriff" , "Veriff verification and review events" , "/api/webhooks/veriff" ],
[ "circle" , "Circle payment, wallet, and transfer events" , "/api/webhooks/circle" ],
];
2026-07-12 22:34:01 -04:00
function envValue ( key ) {
if ( process . env [ key ]) return process . env [ key ];
return ( ENV_ALIASES [ key ] || []). map (( alias ) => process . env [ alias ]). find ( Boolean );
}
2026-07-12 16:50:03 -04:00
function providerStatus () {
return REQUIRED_ENV . map (([ key , label ]) => ({
key ,
label ,
2026-07-12 22:34:01 -04:00
configured : Boolean ( envValue ( key )),
aliases : ENV_ALIASES [ key ] || [],
2026-07-12 16:58:10 -04:00
expected : key === "POLYMARKET_SIGNATURE_TYPE" ? "3 for new deposit-wallet API users" : undefined ,
2026-07-12 16:50:03 -04:00
}));
}
2026-07-14 07:54:50 -04:00
function personalStatus () {
return PERSONAL_REQUIRED_ENV . map (([ key , label ]) => ({
key ,
label ,
configured : Boolean ( envValue ( key )),
expected : key === "POLYMARKET_SIGNATURE_TYPE" ? "3 for deposit-wallet users" : undefined ,
}));
}
2026-07-12 22:34:01 -04:00
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 ,
};
});
}
2026-07-12 16:50:03 -04:00
function liveTradingReady () {
2026-07-12 16:58:10 -04:00
const requiredConfigured = providerStatus (). every (( x ) => x . configured );
const liveFlagEnabled = process . env . LIVE_TRADING_ENABLED === "true" ;
const signatureTypeOk = String ( process . env . POLYMARKET_SIGNATURE_TYPE || "" ) === "3" ;
const chainOk = String ( process . env . POLYMARKET_CHAIN_ID || "137" ) === "137" ;
return requiredConfigured && liveFlagEnabled && signatureTypeOk && chainOk ;
2026-07-12 16:50:03 -04:00
}
2026-07-14 07:54:50 -04:00
function personalTradingReady () {
return personalStatus (). every (( x ) => x . configured )
&& process . env . PERSONAL_TRADING_ENABLED === "true"
&& String ( process . env . POLYMARKET_SIGNATURE_TYPE || "" ) === "3"
&& String ( process . env . POLYMARKET_CHAIN_ID || "137" ) === "137" ;
}
2026-07-12 16:50:03 -04:00
function baseStatus () {
const providers = providerStatus ();
2026-07-13 09:16:50 -04:00
const origin = ( envValue ( "WEBHOOK_BASE_URL" ) || envValue ( "PRODUCTION_APP_URL" ) || "" ). replace ( /\/api\/?$/ , "" ). replace ( /\/$/ , "" );
2026-07-12 16:58:10 -04:00
const liveFlagEnabled = process . env . LIVE_TRADING_ENABLED === "true" ;
const signatureTypeOk = String ( process . env . POLYMARKET_SIGNATURE_TYPE || "" ) === "3" ;
const chainOk = String ( process . env . POLYMARKET_CHAIN_ID || "137" ) === "137" ;
const missing = providers . filter (( x ) => ! x . configured ). map (( x ) => x . label );
2026-07-14 07:54:50 -04:00
const personal = personalStatus ();
const personalMissing = personal . filter (( x ) => ! x . configured ). map (( x ) => x . label );
if ( process . env . PERSONAL_TRADING_ENABLED !== "true" ) personalMissing . push ( "PERSONAL_TRADING_ENABLED=true after your own wallet/CLOB test" );
2026-07-12 16:58:10 -04:00
if ( ! liveFlagEnabled ) missing . push ( "LIVE_TRADING_ENABLED=true after final approval" );
if ( ! signatureTypeOk ) missing . push ( "POLYMARKET_SIGNATURE_TYPE=3 for deposit-wallet users" );
if ( ! chainOk ) missing . push ( "POLYMARKET_CHAIN_ID=137" );
2026-07-12 16:50:03 -04:00
return {
ok : true ,
live_trading_enabled : liveTradingReady (),
2026-07-14 07:54:50 -04:00
personal_trading_enabled : personalTradingReady (),
2026-07-12 16:58:10 -04:00
live_flag_enabled : liveFlagEnabled ,
2026-07-14 07:54:50 -04:00
personal_flag_enabled : process . env . PERSONAL_TRADING_ENABLED === "true" ,
2026-07-12 16:58:10 -04:00
signature_type_ok : signatureTypeOk ,
chain_ok : chainOk ,
2026-07-12 16:50:03 -04:00
locked_reason : liveTradingReady ()
? null
2026-07-12 16:58:10 -04:00
: "Live trading is locked until eligibility, payments, wallet/deposit-wallet signing, Polymarket CLOB credentials, audit storage, monitoring, and LIVE_TRADING_ENABLED=true are configured." ,
2026-07-12 16:50:03 -04:00
providers ,
2026-07-12 22:34:01 -04:00
provider_stack : stackStatus (),
2026-07-14 07:54:50 -04:00
personal_requirements : personal ,
2026-07-12 17:07:07 -04:00
launch_requirements : LAUNCH_REQUIREMENTS . map (([ key , label ]) => ({ key , label })),
2026-07-13 09:16:50 -04:00
webhooks : WEBHOOK_ROUTES . map (([ provider , label , path ]) => ({
provider ,
label ,
path ,
url : origin ? ` ${ origin }${ path } ` : path ,
})),
2026-07-12 16:58:10 -04:00
next_required : missing ,
2026-07-14 07:54:50 -04:00
personal_next_required : personalMissing ,
2026-07-12 16:58:10 -04:00
docs : {
polymarket_overview : "https://docs.polymarket.com/trading/overview" ,
polymarket_quickstart : "https://docs.polymarket.com/trading/quickstart" ,
deposit_wallets : "https://docs.polymarket.com/trading/deposit-wallets" ,
},
2026-07-12 16:50:03 -04:00
};
}
function validateIntent ( intent ) {
const missing = [];
[ "user_id" , "wallet_address" , "agent_id" , "market_id" , "side" , "max_amount" ]. forEach (( key ) => {
if ( ! intent || intent [ key ] === undefined || intent [ key ] === null || intent [ key ] === "" ) missing . push ( key );
});
if ( intent && ! [ "YES" , "NO" ]. includes ( String ( intent . side ). toUpperCase ())) missing . push ( "side must be YES or NO" );
if ( intent && Number ( intent . max_amount ) <= 0 ) missing . push ( "max_amount must be positive" );
return missing ;
}
export default async function handler ( req , res ) {
res . setHeader ( "Cache-Control" , "no-store" );
if ( req . method === "GET" ) {
return res . status ( 200 ). json ( baseStatus ());
}
if ( req . method === "POST" ) {
const body = typeof req . body === "string" ? JSON . parse ( req . body || "{}" ) : ( req . body || {});
const intent = body . intent || body ;
const errors = validateIntent ( intent );
if ( errors . length ) {
return res . status ( 400 ). json ({ ok : false , error : "Invalid order intent" , details : errors });
}
const status = baseStatus ();
const auditEvent = {
at : new Date (). toISOString (),
type : "ORDER_INTENT_DRY_RUN" ,
user_id : String ( intent . user_id ),
wallet_address : String ( intent . wallet_address ),
agent_id : String ( intent . agent_id ),
market_id : String ( intent . market_id ),
side : String ( intent . side ). toUpperCase (),
max_amount : Number ( intent . max_amount ),
execution_mode : intent . execution_mode || "manual_approval" ,
2026-07-14 07:54:50 -04:00
account_scope : intent . account_scope || body . account_scope || "public_readiness" ,
2026-07-12 16:50:03 -04:00
};
2026-07-14 07:54:50 -04:00
if ( auditEvent . account_scope === "personal" ) {
await recordAuditEvent ( "PERSONAL_ORDER_INTENT_STAGED" , auditEvent );
return res . status ( 202 ). json ({
ok : true ,
dry_run : true ,
manual_review_required : true ,
live_order_placed : false ,
message : personalTradingReady ()
? "Personal prerequisites are configured, but this endpoint still stages manual review only."
: "Personal order intent staged. Configure your own deposit wallet/CLOB credentials before any manual live execution." ,
audit_event : auditEvent ,
status ,
});
}
2026-07-12 16:50:03 -04:00
if ( ! status . live_trading_enabled ) {
2026-07-13 16:47:06 -04:00
await recordAuditEvent ( "ORDER_INTENT_BLOCKED" , auditEvent );
2026-07-12 16:50:03 -04:00
return res . status ( 423 ). json ({
ok : false ,
dry_run : true ,
error : status . locked_reason ,
audit_event : auditEvent ,
status ,
});
}
2026-07-13 16:47:06 -04:00
await recordAuditEvent ( "ORDER_INTENT_NOT_IMPLEMENTED" , auditEvent );
2026-07-12 16:50:03 -04:00
return res . status ( 501 ). json ({
ok : false ,
dry_run : true ,
error : "Live trading credentials are configured, but signed order placement is intentionally not implemented yet." ,
audit_event : auditEvent ,
status ,
});
}
res . setHeader ( "Allow" , "GET, POST" );
return res . status ( 405 ). json ({ ok : false , error : "Method not allowed" });
}