mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-07-28 00:17:46 +00:00
Split personal trading cockpit from public site
This commit is contained in:
@@ -7,6 +7,9 @@ PRODUCTION_APP_URL=https://polymarket-site-eta.vercel.app
|
||||
# Keep false until legal/compliance approval, provider credentials, wallet signing,
|
||||
# payment rails, and audit logging are production-ready.
|
||||
LIVE_TRADING_ENABLED=false
|
||||
# Personal-only feature gate. Keep false until your own wallet/CLOB credentials
|
||||
# are configured and tiny manual test orders are reviewed.
|
||||
PERSONAL_TRADING_ENABLED=false
|
||||
|
||||
# Accounts / authentication
|
||||
AUTH_PROVIDER=Clerk
|
||||
|
||||
+33
-8
@@ -1,14 +1,15 @@
|
||||
# Real Money Enablement Roadmap
|
||||
|
||||
This app is currently paper trading only. The Live Money tab and `/api/live`
|
||||
endpoint are readiness scaffolding: they make the required product and backend
|
||||
interfaces visible without moving funds or placing orders.
|
||||
This public app is currently paper trading only. The public site hides live
|
||||
trading controls. The `/personal` route opens a private cockpit for your own
|
||||
wallet, your own money, manual approval, and audit logging.
|
||||
|
||||
## What is configured now
|
||||
|
||||
- `.env.example` lists the environment variables the production app needs.
|
||||
- `/api/live` reports which live-money providers are configured or missing.
|
||||
- `/api/live` accepts order intents only as dry-runs and returns an audit event.
|
||||
- `/api/live` accepts public order intents only as dry-runs and returns an
|
||||
audit event. Personal-mode intents are staged for manual review, not executed.
|
||||
- `/api/accounts` creates, logs into, and saves password-backed paper accounts
|
||||
through Vercel Blob storage.
|
||||
- `/api/config` exposes safe public provider configuration, including the
|
||||
@@ -25,11 +26,35 @@ interfaces visible without moving funds or placing orders.
|
||||
permission settings server-side.
|
||||
- `/api/incident` records incident events and can notify the configured incident
|
||||
webhook after `RISK_ADMIN_TOKEN` is set.
|
||||
- The Live Money tab shows launch checks, wallet/deposit settings, agent risk
|
||||
limits, provider webhook URLs, a dry-run order console, and an audit preview.
|
||||
- The public site hides the live-money tab. `/personal` shows wallet/deposit
|
||||
settings, personal risk limits, provider webhook URLs, a manual intent
|
||||
console, and an audit preview.
|
||||
|
||||
The app still refuses to place live orders. That is intentional until the legal,
|
||||
provider, wallet-signing, reconciliation, and monitoring steps below are done.
|
||||
The app still refuses to place live orders. Personal mode stages manual intents
|
||||
only until your own deposit wallet, CLOB credentials, and tiny test flow are
|
||||
ready.
|
||||
|
||||
## Personal-use path
|
||||
|
||||
Personal mode is narrower than the public business launch:
|
||||
|
||||
- Your own money only.
|
||||
- Your own Polymarket/deposit wallet only.
|
||||
- No outside investors, deposits, pooled funds, or copy-trading customers.
|
||||
- No unattended agent trading.
|
||||
- Every live trade starts as a staged manual intent and must be reviewed before
|
||||
you do anything in Polymarket.
|
||||
- Public `LIVE_TRADING_ENABLED` stays `false`.
|
||||
|
||||
Personal mode still needs:
|
||||
|
||||
- `DEPOSIT_WALLET_ADDRESS`
|
||||
- `POLYMARKET_SIGNATURE_TYPE=3`
|
||||
- `POLYMARKET_CLOB_API_KEY`
|
||||
- `POLYMARKET_CLOB_SECRET`
|
||||
- `POLYMARKET_CLOB_PASSPHRASE`
|
||||
- `RISK_ADMIN_TOKEN`
|
||||
- `PERSONAL_TRADING_ENABLED=true` only after your own tiny manual test flow
|
||||
|
||||
## Required before live deposits or trading
|
||||
|
||||
|
||||
+49
@@ -44,6 +44,16 @@ const REQUIRED_ENV = [
|
||||
["CUSTOMER_SUPPORT_EMAIL", "Customer support contact"],
|
||||
];
|
||||
|
||||
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"],
|
||||
];
|
||||
|
||||
const ENV_ALIASES = {
|
||||
DATABASE_URL: ["NEON_DATABASE_URL"],
|
||||
KYC_API_KEY: ["VERIFF_API_KEY"],
|
||||
@@ -133,6 +143,15 @@ function providerStatus() {
|
||||
}));
|
||||
}
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
function stackStatus() {
|
||||
return PROVIDER_STACK.map((provider) => {
|
||||
const checks = provider.env.map((key) => ({ key, configured: Boolean(envValue(key)) }));
|
||||
@@ -152,6 +171,13 @@ function liveTradingReady() {
|
||||
return requiredConfigured && liveFlagEnabled && signatureTypeOk && chainOk;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
function baseStatus() {
|
||||
const providers = providerStatus();
|
||||
const origin = (envValue("WEBHOOK_BASE_URL") || envValue("PRODUCTION_APP_URL") || "").replace(/\/api\/?$/, "").replace(/\/$/, "");
|
||||
@@ -159,13 +185,18 @@ function baseStatus() {
|
||||
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);
|
||||
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");
|
||||
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");
|
||||
return {
|
||||
ok: true,
|
||||
live_trading_enabled: liveTradingReady(),
|
||||
personal_trading_enabled: personalTradingReady(),
|
||||
live_flag_enabled: liveFlagEnabled,
|
||||
personal_flag_enabled: process.env.PERSONAL_TRADING_ENABLED === "true",
|
||||
signature_type_ok: signatureTypeOk,
|
||||
chain_ok: chainOk,
|
||||
locked_reason: liveTradingReady()
|
||||
@@ -173,6 +204,7 @@ function baseStatus() {
|
||||
: "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(),
|
||||
personal_requirements: personal,
|
||||
launch_requirements: LAUNCH_REQUIREMENTS.map(([key, label]) => ({ key, label })),
|
||||
webhooks: WEBHOOK_ROUTES.map(([provider, label, path]) => ({
|
||||
provider,
|
||||
@@ -181,6 +213,7 @@ function baseStatus() {
|
||||
url: origin ? `${origin}${path}` : path,
|
||||
})),
|
||||
next_required: missing,
|
||||
personal_next_required: personalMissing,
|
||||
docs: {
|
||||
polymarket_overview: "https://docs.polymarket.com/trading/overview",
|
||||
polymarket_quickstart: "https://docs.polymarket.com/trading/quickstart",
|
||||
@@ -225,8 +258,24 @@ export default async function handler(req, res) {
|
||||
side: String(intent.side).toUpperCase(),
|
||||
max_amount: Number(intent.max_amount),
|
||||
execution_mode: intent.execution_mode || "manual_approval",
|
||||
account_scope: intent.account_scope || body.account_scope || "public_readiness",
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
if (!status.live_trading_enabled) {
|
||||
await recordAuditEvent("ORDER_INTENT_BLOCKED", auditEvent);
|
||||
return res.status(423).json({
|
||||
|
||||
+1
-3
@@ -10,9 +10,7 @@ function validateProfile(profile) {
|
||||
if (Number(permission.maxAllocation || 0) < 0) errors.push(`${agentId} maxAllocation must be 0 or greater`);
|
||||
const maxPositionPct = Number(permission.maxPositionPct || 0);
|
||||
if (maxPositionPct < 0 || maxPositionPct > 100) errors.push(`${agentId} maxPositionPct must be between 0 and 100`);
|
||||
if (permission.autoTrade && !permission.manualApprove) {
|
||||
errors.push(`${agentId} cannot enable unattended real-money trading before final approval`);
|
||||
}
|
||||
if (permission.autoTrade) errors.push(`${agentId} cannot enable unattended real-money trading in personal mode`);
|
||||
});
|
||||
|
||||
return errors;
|
||||
|
||||
+44
-37
@@ -69,7 +69,8 @@ h1,h2,h3,.brand-name{font-family:'Space Grotesk','Inter',sans-serif}
|
||||
.btn.primary:hover{filter:brightness(1.08)}
|
||||
.btn:disabled{opacity:.55;cursor:wait;transform:none}
|
||||
.btn.ghost{background:transparent}
|
||||
.personal-mode [data-tab="invest"],.personal-mode [data-tab="live"]{display:none!important}
|
||||
body:not(.personal-mode) [data-tab="live"]{display:none!important}
|
||||
.personal-mode [data-tab="invest"]{display:none!important}
|
||||
.personal-banner{display:none;margin:0 0 14px;border:1px solid rgba(251,191,36,.22);border-radius:14px;
|
||||
padding:12px 14px;background:rgba(251,191,36,.08);color:#f8e7b0;font-size:13px}
|
||||
.personal-mode .personal-banner{display:block}
|
||||
@@ -337,7 +338,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
</div>
|
||||
</nav>
|
||||
<div class="personal-banner" id="personalBanner">
|
||||
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders for you.
|
||||
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
|
||||
</div>
|
||||
|
||||
<!-- ============ OVERVIEW ============ -->
|
||||
@@ -545,18 +546,19 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ LIVE MONEY READINESS ============ -->
|
||||
<!-- ============ PERSONAL TRADING COCKPIT ============ -->
|
||||
<section class="tabpanel" data-tab="live">
|
||||
<div class="section-title">🔐 Live Money Readiness</div>
|
||||
<div class="section-title">🔐 Personal Trading Cockpit</div>
|
||||
<div class="disclaimer" style="margin-bottom:16px"><b>Private use only.</b> This area is hidden from the public site and is for your own wallet, your own money, and manual approval only. It does not accept investor deposits, pool funds, sell agent access, or execute unattended trades.</div>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Launch checklist</h3><span class="small muted" id="liveReadyPct"></span></div>
|
||||
<div class="card-h"><h3>Personal safety checklist</h3><span class="small muted" id="liveReadyPct"></span></div>
|
||||
<div id="liveChecklist"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>User wallet & funds</h3><span class="small muted">simulation only</span></div>
|
||||
<div class="card-h"><h3>Your wallet & limits</h3><span class="small muted">personal only</span></div>
|
||||
<div class="account-row">
|
||||
<input class="input" id="liveWalletAddress" placeholder="Wallet / deposit wallet address" />
|
||||
<input class="input" id="liveWalletAddress" placeholder="Your Polymarket deposit wallet address" />
|
||||
<select class="select" id="liveJurisdiction">
|
||||
<option value="">Jurisdiction</option><option>United States</option><option>Canada</option><option>European Union</option><option>Other</option>
|
||||
</select>
|
||||
@@ -565,35 +567,35 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<input class="input" id="liveDepositLimit" type="number" min="0" step="100" placeholder="Max deposit" />
|
||||
<input class="input" id="liveWithdrawReserve" type="number" min="0" step="100" placeholder="Withdrawal reserve" />
|
||||
<button class="btn" id="saveLiveProfileBtn">Save profile</button>
|
||||
<button class="btn primary" disabled>Real deposits locked</button>
|
||||
<button class="btn primary" disabled>No public deposits</button>
|
||||
</div>
|
||||
<div class="risk-note" style="margin-top:12px">Real deposits stay locked until eligibility, wallet signing, payment rails, and legal approval are complete.</div>
|
||||
<div class="risk-note" style="margin-top:12px">Fund only your own wallet directly through approved provider flows. This site will not accept, hold, or withdraw money for other people.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Agent permissions & risk limits</h3><span class="small muted">what a live account would allow</span></div>
|
||||
<div class="card-h"><h3>Agent permissions & risk limits</h3><span class="small muted">manual approval only</span></div>
|
||||
<div id="liveAgentPermissions"></div>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Execution console</h3><span class="small muted">locked until backend exists</span></div>
|
||||
<div class="card-h"><h3>Manual order console</h3><span class="small muted">review before any trade</span></div>
|
||||
<div id="liveBackendStatus" class="locked-panel">Checking backend readiness...</div>
|
||||
<div class="account-row">
|
||||
<select class="select" id="liveIntentAgent"></select>
|
||||
<input class="input" id="liveIntentMarket" placeholder="Market ID" />
|
||||
<select class="select" id="liveIntentSide"><option>YES</option><option>NO</option></select>
|
||||
<input class="input" id="liveIntentAmount" type="number" min="1" step="25" placeholder="Max amount" />
|
||||
<button class="btn" id="dryRunOrderBtn">Dry-run order intent</button>
|
||||
<button class="btn" id="dryRunOrderBtn">Stage manual intent</button>
|
||||
</div>
|
||||
<ol class="steps">
|
||||
<li><b>Wallet signature</b> — user signs EIP-712 orders; the site never signs unauthorized trades.</li>
|
||||
<li><b>CLOB router</b> — backend submits, cancels, and tracks orders through authenticated Polymarket APIs.</li>
|
||||
<li><b>Fill sync</b> — real positions, cash, unsettled orders, and agent actions reconcile continuously.</li>
|
||||
<li><b>Emergency controls</b> — user can pause agents, cancel orders, and withdraw available funds any time.</li>
|
||||
<li><b>Research signal</b> — choose an agent idea and inspect the exact market yourself.</li>
|
||||
<li><b>Manual review</b> — confirm side, amount, price, risk cap, and current market status.</li>
|
||||
<li><b>Your wallet only</b> — sign or submit only from your own Polymarket/deposit wallet.</li>
|
||||
<li><b>Emergency pause</b> — keep agent automation off and cancel staged intents any time.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Audit trail preview</h3><span class="small muted">required for real money</span></div>
|
||||
<div class="card-h"><h3>Personal audit trail</h3><span class="small muted">local + Neon records</span></div>
|
||||
<div id="liveOrderDryRun"></div>
|
||||
<div id="liveAuditTrail"></div>
|
||||
</div>
|
||||
@@ -616,8 +618,8 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Server controls</h3><span class="small muted">live safety gates</span></div>
|
||||
<div class="locked-panel">
|
||||
<b>Production switch remains off</b>
|
||||
<div class="small muted">The backend can now store policies, consent, risk profiles, webhooks, incidents, and dry-run order intents. Real deposits and live orders still require final approval and real CLOB/deposit-wallet setup.</div>
|
||||
<b>Public production switch remains off</b>
|
||||
<div class="small muted">The public product remains paper trading only. Personal mode can store your wallet limits, consent status, staged manual intents, webhooks, and incident records, but does not onboard investors or pool money.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -720,7 +722,7 @@ function applyPersonalMode(){
|
||||
if(sub)sub.textContent="private research · paper tracking · manual links";
|
||||
const heroTitle=document.querySelector(".hero h1"),heroCopy=document.querySelector(".hero p");
|
||||
if(heroTitle)heroTitle.innerHTML=`Your private agent lab for <span class="grad">manual market research</span>`;
|
||||
if(heroCopy)heroCopy.textContent="Use the same agents, charts, paper portfolios, and Polymarket links for your own research. Nothing here pools outside money, manages investor funds, or places live orders.";
|
||||
if(heroCopy)heroCopy.textContent="Use the same agents, charts, paper portfolios, and Polymarket links for your own research. Nothing here pools outside money, manages investor funds, or places live orders without manual review.";
|
||||
document.querySelectorAll(".hbadge").forEach((b,i)=>{
|
||||
const labels=["Private research mode","Manual execution only","Cloud paper accounts","Live market links"];
|
||||
if(labels[i])b.textContent=labels[i];
|
||||
@@ -2331,19 +2333,18 @@ function renderAbout(){
|
||||
<div class="fic">${a.emoji}</div><h4>${a.name}</h4><p>${agentBlurb(a,st)}</p></div>`).join("");
|
||||
}
|
||||
const LIVE_CHECKS=[
|
||||
["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"],
|
||||
["wallet","User-controlled wallet/deposit-wallet signing and permission revocation implemented"],
|
||||
["payments","Deposits, withdrawals, webhooks, failed-payment handling, and support flows implemented"],
|
||||
["clob","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"],
|
||||
["personal_scope","I will use only my own money and my own Polymarket/deposit wallet"],
|
||||
["no_investors","No outside investors, pooled funds, user deposits, or copy-trading customers"],
|
||||
["eligibility","I personally satisfy the provider and Polymarket eligibility/location rules"],
|
||||
["wallet","My own deposit wallet is created, controlled by me, and funded only by me"],
|
||||
["clob","Personal CLOB credentials are configured only for my own wallet"],
|
||||
["manual_only","Every staged intent requires manual review before any order is placed"],
|
||||
["reconciliation","I will compare cash, open orders, fills, and positions after each test"],
|
||||
["risk","Personal max amount, per-agent caps, stop rules, and emergency pause are set"],
|
||||
["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"],
|
||||
["records","Append-only audit logs are recording profile changes and staged intents"],
|
||||
["operations","Incident alerting and emergency pause path are ready"],
|
||||
["testing","Tiny manual test trades are checked before increasing size"],
|
||||
];
|
||||
function defaultLiveState(){
|
||||
const checks={};LIVE_CHECKS.forEach(([k])=>checks[k]=false);
|
||||
@@ -2386,10 +2387,12 @@ function renderLiveBackendStatus(){
|
||||
const s=LIVE_BACKEND_STATUS;
|
||||
if(!s){root.innerHTML="Checking backend readiness...";if(hookRoot)hookRoot.innerHTML="Checking webhook setup...";if(policyRoot)policyRoot.innerHTML="Checking policy status...";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 personalReqs=(s.personal_requirements||[]).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?"ready":"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 personalNext=(s.personal_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("");
|
||||
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>`;
|
||||
root.innerHTML=`<b>${s.personal_trading_enabled?"Personal prerequisites ready":"Manual personal mode locked to staging"}</b><div class="small muted" style="margin:6px 0">${esc(s.locked_reason||"Public trading remains locked.")}</div>${personalReqs?`<div class="locked-panel" style="margin-top:10px"><b>Personal-only requirements</b>${personalReqs}</div>`:""}${personalNext?`<div class="locked-panel" style="margin-top:10px"><b>Before personal live execution</b><ul>${personalNext}</ul></div>`:""}${stack?`<div class="locked-panel" style="margin-top:10px"><b>Public provider stack</b>${stack}</div>`:""}${providers}${next?`<div class="locked-panel" style="margin-top:10px"><b>Public platform blockers</b><ul>${next}</ul></div>`:""}${reqs?`<div class="locked-panel" style="margin-top:10px"><b>Public launch requirements</b><ol>${reqs}</ol></div>`:""}<div class="small muted" style="margin-top:8px">This cockpit stages manual intents only. Public investor deposits and unattended agent trading stay off.</div>`;
|
||||
if(hookRoot){
|
||||
const hooks=(s.webhooks||[]).map(h=>`<div class="leader-row"><span><b>${esc(h.provider)}</b><div class="small muted">${esc(h.label)}</div></span><a class="market-link" href="${esc(h.url)}" target="_blank" rel="noopener">${esc(h.url)}</a></div>`).join("");
|
||||
hookRoot.innerHTML=hooks||"Webhook URLs will appear after backend readiness loads.";
|
||||
@@ -2411,24 +2414,27 @@ async function dryRunLiveOrderIntent(){
|
||||
side:$("liveIntentSide").value,
|
||||
max_amount:Number($("liveIntentAmount").value||0),
|
||||
execution_mode:"manual_approval",
|
||||
account_scope:PERSONAL_MODE?"personal":"public_readiness",
|
||||
};
|
||||
try{
|
||||
const r=await fetch("/api/live",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({intent})});
|
||||
const d=await r.json();
|
||||
$("liveOrderDryRun").innerHTML=`<div class="locked-panel"><b>Order intent dry-run</b><div class="small muted">${esc(d.error||"Accepted dry-run.")}</div><pre style="white-space:pre-wrap;font-size:11px;color:#cdd6e8">${esc(JSON.stringify(d.audit_event||d,null,2))}</pre></div>`;
|
||||
saveLiveState(s,`Dry-ran ${intent.side} order intent for ${intent.agent_id} on ${intent.market_id}`);
|
||||
saveLiveState(s,`${PERSONAL_MODE?"Staged manual":"Dry-ran"} ${intent.side} order intent for ${intent.agent_id} on ${intent.market_id}`);
|
||||
}catch(e){
|
||||
toast("Order dry-run failed.");
|
||||
}
|
||||
}
|
||||
async function saveRiskProfileToServer(s){
|
||||
const permissions=JSON.parse(JSON.stringify(s.agents||{}));
|
||||
Object.values(permissions).forEach(p=>{p.autoTrade=false;p.manualApprove=true;});
|
||||
const body={
|
||||
user_id:"local-readiness-user",
|
||||
wallet_address:s.walletAddress,
|
||||
jurisdiction:s.jurisdiction,
|
||||
deposit_limit:s.depositLimit,
|
||||
withdraw_reserve:s.withdrawReserve,
|
||||
agent_permissions:s.agents,
|
||||
agent_permissions:permissions,
|
||||
};
|
||||
const r=await fetch("/api/risk-profile",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
|
||||
const d=await r.json();
|
||||
@@ -2473,7 +2479,7 @@ function renderLiveMoneyTab(){
|
||||
<div><div class="invest-name">${esc(a.name)}</div><div class="invest-copy">${esc(a.blurb.slice(0,120))}${a.blurb.length>120?"...":""}</div></div>
|
||||
<div class="trade-actions">
|
||||
<label class="small muted"><input type="checkbox" data-live-agent="${a.id}" data-field="enabled" ${p.enabled?"checked":""}> enabled</label>
|
||||
<label class="small muted"><input type="checkbox" data-live-agent="${a.id}" data-field="autoTrade" ${p.autoTrade?"checked":""}> auto</label>
|
||||
<label class="small muted"><input type="checkbox" data-live-agent="${a.id}" data-field="autoTrade" ${p.autoTrade?"checked":""} disabled> auto unavailable</label>
|
||||
<input class="input" type="number" min="0" step="100" value="${p.maxAllocation||""}" placeholder="Max $" data-live-agent="${a.id}" data-field="maxAllocation">
|
||||
<input class="input" type="number" min="1" max="100" step="1" value="${p.maxPositionPct||10}" placeholder="Max %" data-live-agent="${a.id}" data-field="maxPositionPct">
|
||||
</div>
|
||||
@@ -2490,7 +2496,8 @@ function esc(s){return String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<","
|
||||
const TABS=["overview","leaderboard","suggestions","portfolio","invest","paper","live","about"];
|
||||
function showTab(name){
|
||||
if(!TABS.includes(name))name="overview";
|
||||
if(PERSONAL_MODE&&["invest","live"].includes(name))name="overview";
|
||||
if(PERSONAL_MODE&&name==="invest")name="overview";
|
||||
if(!PERSONAL_MODE&&name==="live")name="overview";
|
||||
document.querySelectorAll(".tabpanel").forEach(p=>p.classList.toggle("active",p.dataset.tab===name));
|
||||
document.querySelectorAll(".tab").forEach(t=>t.classList.toggle("active",t.dataset.tab===name));
|
||||
if(location.hash.slice(1)!==name)history.replaceState(null,"","#"+name);
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Personal Polymarket Arena</title>
|
||||
<meta http-equiv="refresh" content="0; url=/?personal=1#overview" />
|
||||
<script>location.replace("/?personal=1#overview");</script>
|
||||
<meta http-equiv="refresh" content="0; url=/?personal=1#live" />
|
||||
<script>location.replace("/?personal=1#live");</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Opening personal research mode...</p>
|
||||
|
||||
Reference in New Issue
Block a user