'use client' import { useEffect, useState } from 'react' import Link from 'next/link' import { useAccount, useSignMessage } from 'wagmi' import { useDashboardStore } from '@/store/dashboard' import { getUser, getUserPublic, setUserSettings, type UserSettings } from '@/lib/api' import { getOrCreateViewEnvelope, signRequest } from '@/lib/signedRequest' import BotPanel from '@/components/dashboard/BotPanel' const ACTION_SET_SETTINGS = 'set_settings' const ACTION_VIEW_USER = 'view_user' const DEFAULT_SETTINGS: UserSettings = { leverage: 3, position_size_usd: 20, take_profit_pct: null, stop_loss_pct: null, min_confidence: 80, } function LockedCard({ label }: { label: string }) { return (
{label}
) } function BotSettings({ connected, subscribed, wallet, initial, }: { connected: boolean subscribed: boolean wallet?: string initial: UserSettings }) { if (!connected) { return (

Connect wallet to configure

Your wallet is used to verify access and sign bot commands. We never custody your funds.

) } if (!subscribed) { return (

Subscribe to unlock the bot

Get automated execution, real-time alerts, and advanced configuration.

14-day free trial
{['Auto-execute trades on your exchange', 'SMS + Telegram alerts within 2s', 'Custom confidence + position-size rules', 'Backtest any strategy on 2y of data'].map(f => (
{f}
))}
Activate on dashboard →
) } return } function BotSettingsForm({ wallet, initial }: { wallet: string; initial: UserSettings }) { const { signMessageAsync } = useSignMessage() const [s, setS] = useState(initial) const [useTp, setUseTp] = useState(initial.take_profit_pct != null) const [useSl, setUseSl] = useState(initial.stop_loss_pct != null) const [state, setState] = useState<'idle' | 'signing' | 'saving' | 'ok' | 'err'>('idle') const [err, setErr] = useState('') // Re-sync when initial changes (after /user fetch resolves) useEffect(() => { setS(initial) setUseTp(initial.take_profit_pct != null) setUseSl(initial.stop_loss_pct != null) }, [initial]) const dirty = JSON.stringify(s) !== JSON.stringify(initial) || useTp !== (initial.take_profit_pct != null) || useSl !== (initial.stop_loss_pct != null) async function save() { setErr('') try { const payload: UserSettings = { ...s, take_profit_pct: useTp ? s.take_profit_pct ?? 2 : null, stop_loss_pct: useSl ? s.stop_loss_pct ?? 1.5 : null, } setState('signing') const env = await signRequest({ action: ACTION_SET_SETTINGS, wallet, body: payload, signMessageAsync, }) setState('saving') await setUserSettings(env, payload) setS(payload) setState('ok') setTimeout(() => setState('idle'), 2000) } catch (e: unknown) { const m = e instanceof Error ? e.message : 'Save failed' setErr(m.includes('User rejected') || m.includes('denied') ? 'Signature cancelled' : m.slice(0, 140)) setState('err') } } const label = state === 'signing' ? 'Waiting for signature…' : state === 'saving' ? 'Saving…' : state === 'ok' ? '✓ Saved' : 'Save changes' return (

Execution

Position size (USD)
Fixed notional per trade. Margin = size / leverage.
setS({ ...s, position_size_usd: Math.max(5, +e.target.value || 0) })} style={{ width: 140 }} />
margin ≈ ${(s.position_size_usd / s.leverage).toFixed(2)}
Leverage
Isolated margin, 1–50×. Higher = bigger PnL swings.
setS({ ...s, leverage: +e.target.value })} style={{ flex: 1, accentColor: 'var(--amber)' }} />
{s.leverage}×
Minimum AI confidence
Platform floor is 80%. Raise to be pickier; can't lower.
setS({ ...s, min_confidence: +e.target.value })} style={{ flex: 1, accentColor: 'var(--amber)' }} />
{s.min_confidence}%

Risk management

Take profit
Auto-close when price moves your way by this %.
setS({ ...s, take_profit_pct: +e.target.value })} style={{ width: 100 }} /> % gain
Stop loss
Auto-close when price moves against you by this %.
setS({ ...s, stop_loss_pct: +e.target.value })} style={{ width: 100 }} /> % loss
{!dirty && state === 'idle' && No changes to save} {state === 'err' && {err}}
) } function ExchangeSettings({ subscribed }: { subscribed: boolean }) { if (!subscribed) return return } function AccountSettings({ address }: { address?: string }) { return (

Account

) } export default function SettingsClient() { const [section, setSection] = useState('bot') const { address, isConnected } = useAccount() const { signMessageAsync } = useSignMessage() const { isSubscribed, setSubscribed, setHlApiKeySet } = useDashboardStore() const [settings, setSettings] = useState(DEFAULT_SETTINGS) const [loadErr, setLoadErr] = useState('') useEffect(() => { if (!isConnected || !address) return let cancelled = false // First fetch public state (no sig). Only request a view signature if user // is actually subscribed — otherwise there's nothing private to fetch. ;(async () => { try { const pub = await getUserPublic(address.toLowerCase()) if (cancelled) return setSubscribed(pub.active) setHlApiKeySet(pub.hl_api_key_set) if (!pub.active) return const env = await getOrCreateViewEnvelope({ action: ACTION_VIEW_USER, wallet: address, signMessageAsync, }) if (cancelled) return const u = await getUser(address, env) if (cancelled) return setSubscribed(u.active) setHlApiKeySet(u.hl_api_key_set, u.hl_api_key_masked ?? undefined) if (u.settings) setSettings(u.settings) } catch (e: unknown) { const m = e instanceof Error ? e.message : 'Failed to load settings' // Swallow cancelled signatures; surface real failures if (!m.includes('User rejected') && !m.includes('denied')) { setLoadErr(m.slice(0, 140)) } } })() return () => { cancelled = true } }, [address, isConnected, setSubscribed, setHlApiKeySet, signMessageAsync]) const sections = [ { key: 'bot', label: 'Trading bot' }, { key: 'exchange', label: 'Exchange keys' }, { key: 'account', label: 'Account' }, ] return (
{sections.map(s => ( ))}
{loadErr && (
{loadErr}
)} {section === 'bot' && } {section === 'exchange' && } {section === 'account' && }
) }