'use client' import Link from 'next/link' import { useState, useEffect, useCallback } from 'react' import { useLocale } from 'next-intl' import { useAccount, useSignMessage } from 'wagmi' import { getUserPublic, setAutoTrade, type UserPublic } from '@/lib/api' import { signRequest } from '@/lib/signedRequest' import { isUserRejection, walletErrorLabel } from '@/lib/walletError' import { confirmSign } from '@/components/wallet/SignConfirmSheet' /** * ONE control: the master Auto-Trade switch. * * OFF (default, safe) — signals are still scanned & shown in the feed, * but NO trade is opened. Pure monitoring. * ON — a qualifying signal auto-opens a trade (full * System-2 risk: dynamic leverage, staged de-risk, * ratchet, peak-trail). De-risk / stop-loss always * run on open positions regardless — never toggleable. * * Replaces the old scanner-toggle / timed manual-window / schedule / paper * trio. Circuit breaker is read-only; turning Auto-Trade ON acknowledges + * clears a tripped breaker. Per-trade pyramiding is the "Grow" switch on * each open position (Trades page), not here. */ const SYS = { trump: { idx: '①', name: 'Trump', accent: '#b45309', soft: 'rgba(180,83,9,0.08)' }, btc: { idx: '②', name: 'Macro Vibes', accent: '#16a34a', soft: 'rgba(22,163,74,0.08)' }, } as const const ROW: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '8px 0', flexWrap: 'wrap', } const LABEL: React.CSSProperties = { fontSize: 13, color: 'var(--ink-2)' } export default function SystemControl({ system }: { system: 'trump' | 'btc' }) { const locale = useLocale() const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json const s = SYS[system] const { address, isConnected } = useAccount() const { signMessageAsync } = useSignMessage() const [mounted, setMounted] = useState(false) const [pub, setPub] = useState(null) const [busy, setBusy] = useState(false) const [err, setErr] = useState('') useEffect(() => { setMounted(true) }, []) const refresh = useCallback(async () => { if (!address) { setPub(null); return } const p = await getUserPublic(address.toLowerCase()).catch(() => null) setPub(p) }, [address]) useEffect(() => { refresh() const poll = setInterval(refresh, 30_000) return () => clearInterval(poll) }, [refresh]) const subscribed = !!pub?.active const paper = !!pub?.paper_mode const autoOn = !!pub?.auto_trade const cbTripped = !!pub?.circuit_breaker_tripped_at && (Date.now() - new Date(pub!.circuit_breaker_tripped_at!).getTime()) < 24 * 3600 * 1000 async function flipAuto(on: boolean) { if (busy) return if (!address) { setErr(isZh ? '请先连接右上角的钱包。' : 'Connect your wallet first (top-right).'); return } if (!subscribed) { setErr(isZh ? '钱包尚未订阅,请先去设置页完成订阅。' : 'Wallet not subscribed — subscribe on the Settings page first.'); return } if (autoOn === on) return // Show pre-signing confirmation sheet before triggering MetaMask const confirmed = await confirmSign(on ? { label: paper ? (isZh ? '开启 Auto-Trade(模拟模式)' : 'Enable Auto-Trade (paper mode)') : (isZh ? '开启 Auto-Trade(真实资金)' : 'Enable Auto-Trade (live capital)'), description: paper ? (isZh ? '机器人将在信号触发时自动开仓(Paper 模式,无真实资金)。钱包签名仅用于身份验证。' : 'The bot will auto-open positions when a qualifying signal fires in paper mode. No real capital is used. The wallet signature is only for identity verification.') : (isZh ? '机器人将在 Hyperliquid 上用真实资金自动开仓。止损与分级减仓始终生效保护仓位。' : 'The bot will auto-open real positions on Hyperliquid. Stop-loss and staged de-risking remain active to protect the position.'), danger: !paper, } : { label: isZh ? '关闭 Auto-Trade' : 'Disable Auto-Trade', description: isZh ? '停止自动开仓。已有持仓的止损与减仓逻辑仍会继续运行。' : 'Stop opening new trades automatically. Risk controls on existing positions will keep running.', danger: false, } ) if (!confirmed) return setErr(''); setBusy(true) try { const env = await signRequest({ action: 'set_auto_trade', wallet: address, body: { enabled: on }, signMessageAsync, }) const r = await setAutoTrade(env, on) setPub(p => p ? { ...p, auto_trade: r.auto_trade, ...(r.circuit_breaker_cleared ? { circuit_breaker_tripped_at: null, circuit_breaker_reason: null } : {}), } : p) } catch (e) { if (isUserRejection(e)) { setErr(isZh ? '已取消' : 'Cancelled') } else { setErr(walletErrorLabel(e, isZh ? '已取消' : 'Cancelled', 110) || (isZh ? 'Auto-Trade 切换失败' : 'Auto-Trade toggle failed')) } } finally { setBusy(false) } } // SSR / pre-hydration: render nothing to avoid hydration mismatch with // wagmi state that resolves client-side. if (!mounted) return null const settingsHref = `/${locale}/settings#bot-config` const settingsLabel = system === 'trump' ? 'Trump Signal' : 'Macro Vibes' // Wallet not connected: a previous version showed a giant full-width // "Connect a wallet..." card here, which was redundant with the navbar's // Connect button. The opposite extreme — rendering null — silently hid // the existence of Auto-Trade for unconnected users. Compromise: a SLIM // inline strip that simply announces the feature without pushing the // page around. Once connected, the full control panel renders below. if (!isConnected) { return (
{s.name} Auto-Trade · OFF — connect wallet (top-right) to enable
Settings {settingsLabel}
) } // ── Net result — the single sentence that matters ───────────────────────── let netOk = false let netMsg = '' if (!subscribed) { netMsg = isZh ? '钱包还没订阅,请先去设置页完成订阅。' : 'Wallet not subscribed — subscribe on the Settings page first.' } else if (cbTripped) { netMsg = isZh ? `熔断器已触发(${pub?.circuit_breaker_reason || '风险限制'})。重新打开 Auto-Trade 才会确认并恢复。` : `Circuit breaker tripped (${pub?.circuit_breaker_reason || 'risk limit'}). Turn Auto-Trade ON to acknowledge & resume.` } else if (!autoOn) { netMsg = isZh ? 'Auto-Trade 当前为关闭(全局): Trump 和 BTC 信号仍会显示,但不会自动开仓。' : 'Auto-Trade is OFF (global) — Trump AND Macro Vibes signals are shown in the feed but NOT traded.' } else { netOk = true netMsg = isZh ? `Auto-Trade 已开启(全局,覆盖 Trump 和 BTC): 下一条合格信号会自动开出${paper ? '模拟' : '真实'}仓位。` : `Auto-Trade ON (global, both Trump & BTC) — will open a ${paper ? 'PAPER' : 'LIVE'} trade on the next qualifying signal.` } const Pill = ({ active, onClick, children, tone }: { active: boolean; onClick: () => void; children: React.ReactNode tone: 'green' | 'idle' }) => { const bg = active ? (tone === 'green' ? 'var(--up, #16a34a)' : 'var(--ink-3)') : 'transparent' return ( ) } return (
{/* Header */}
{s.idx} {s.name} {isZh ? '控制面板' : '— control'}
{/* Master Auto-Trade switch */}
Auto-Trade {isZh ? '· 全局(Trump + BTC)' : '· GLOBAL (Trump + BTC)'}
{isZh ? ( <> 一个开关控制两套系统。 关闭时:信号仍会扫描并显示,但不会自动交易。 开启时:满足条件的 Trump 或 BTC 信号都会自动开仓。无论开关状态如何, 已开仓位的止损和分级降风险都会继续保护持仓。 ) : ( <> One switch for both systems. OFF: signals scanned & shown in the feed, nothing traded. ON: a qualifying Trump OR Macro Vibes signal auto-opens a trade. Stop-loss / staged de-risk always protect open positions either way. )}
flipAuto(true)} tone="green">ON flipAuto(false)} tone="idle">OFF
{/* Read-only status */}
{isZh ? '执行模式' : 'Execution mode'} {paper ? (isZh ? '📝 模拟' : '📝 PAPER') : (isZh ? '💰 真实资金' : '💰 LIVE')} {isZh ? '· 订阅时确定' : '· set when subscribing'}
{isZh ? '熔断器' : 'Circuit breaker'} {cbTripped ? (isZh ? `🚨 已触发(${pub?.circuit_breaker_reason})` : `🚨 tripped (${pub?.circuit_breaker_reason})`) : (isZh ? '✓ 正常' : '✓ clear')}
{err && (
● {err}
)}
{/* Net result */}
{isZh ? '⟹ 当前状态' : '⟹ Current status'}
{netOk ? '✅ ' : '⛔ '}{netMsg}
Settings {settingsLabel}
) }