import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { AlertCircle, ArrowRight, CircleDollarSign, Download, CheckCircle2, Copy, ExternalLink, KeyRound, Loader2, RefreshCw, ShieldCheck, Wallet, Zap, } from 'lucide-react' import { toast } from 'sonner' import { api } from '../../lib/api' import { buildDashboardPath, ROUTES } from '../../router/paths' import type { AIModel, CurrentBeginnerWalletResponse, Exchange, ExchangeAccountState, TraderInfo, } from '../../types' import { HyperliquidWalletConnect } from '../common/HyperliquidWalletConnect' type LaunchStepStatus = 'ready' | 'action' | 'blocked' interface AutopilotLaunchPanelProps { models: AIModel[] exchanges: Exchange[] exchangeAccountStates: Record traders?: TraderInfo[] isLoggedIn: boolean language: string onRefresh: () => Promise onOpenClaw402Config?: () => void onOpenHyperliquidConfig?: () => void } const MIN_AI_FEE_USDC = 1 const MIN_TRADING_USDC = 12 function parseNumber(value?: string | number) { if (typeof value === 'number') return Number.isFinite(value) ? value : 0 if (!value) return 0 const parsed = Number(value.replace(/[,$\s]/g, '')) return Number.isFinite(parsed) ? parsed : 0 } function shortAddress(address?: string) { if (!address) return '--' return `${address.slice(0, 6)}…${address.slice(-4)}` } function formatUSDC(value: number) { return new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(value) } async function copyText(value: string, label: string) { try { await navigator.clipboard.writeText(value) toast.success(`${label} copied`) } catch { toast.error('Copy failed') } } function BeginnerHyperliquidGuide({ hasInjectedWallet, }: { hasInjectedWallet: boolean }) { const steps = [ { title: 'Prepare an EVM wallet', detail: hasInjectedWallet ? 'Wallet extension detected. Unlock it, then connect below.' : 'Install Rabby or MetaMask, create or import a wallet, then return here.', icon: Wallet, }, { title: 'Open Hyperliquid', detail: 'Use the same wallet on Hyperliquid. Deposit USDC there as trading collateral.', icon: CircleDollarSign, }, { title: 'Authorize NOFX', detail: 'Back in NOFX, approve the Agent and builder fee. NOFX stores the Agent key, not your main wallet key.', icon: KeyRound, }, ] return (
New to Hyperliquid?

Start here if you do not have a trading wallet or have never used Hyperliquid before.

{hasInjectedWallet ? 'Wallet detected' : 'Wallet needed'}
{steps.map((step, index) => { const Icon = step.icon return (
{index + 1}. {step.title}

{step.detail}

) })}
{!hasInjectedWallet ? ( <> Install Rabby MetaMask ) : null} Open Hyperliquid
) } export function AutopilotLaunchPanel({ models, exchanges, exchangeAccountStates, traders = [], isLoggedIn, language, onRefresh, onOpenClaw402Config, onOpenHyperliquidConfig, }: AutopilotLaunchPanelProps) { const navigate = useNavigate() const [wallet, setWallet] = useState( null ) const [walletLoading, setWalletLoading] = useState(false) const [launching, setLaunching] = useState(false) const [refreshing, setRefreshing] = useState(false) const [hasInjectedWallet, setHasInjectedWallet] = useState(false) const isZh = language === 'zh' useEffect(() => { setHasInjectedWallet( typeof window !== 'undefined' && Boolean((window as Window & { ethereum?: unknown }).ethereum) ) }, []) const claw402Model = useMemo( () => models.find( (model) => model.provider === 'claw402' && model.enabled && (model.has_api_key || model.apiKey || model.walletAddress) ) || null, [models] ) const feeWalletAddress = claw402Model?.walletAddress || wallet?.address || '' const feeWalletBalance = parseNumber( claw402Model?.balanceUsdc || wallet?.balance_usdc ) const feeReady = Boolean(feeWalletAddress) && feeWalletBalance >= MIN_AI_FEE_USDC const hyperliquidExchange = useMemo( () => exchanges.find( (exchange) => exchange.exchange_type === 'hyperliquid' && exchange.enabled && Boolean(exchange.hyperliquidWalletAddr) && Boolean(exchange.hyperliquidBuilderApproved) ) || null, [exchanges] ) const hyperliquidConnected = Boolean(hyperliquidExchange) const exchangeState = hyperliquidExchange ? exchangeAccountStates[hyperliquidExchange.id] : undefined const tradingBalance = parseNumber( exchangeState?.available_balance ?? exchangeState?.total_equity ) const tradingBalanceReady = hyperliquidConnected && exchangeState?.status === 'ok' && tradingBalance >= MIN_TRADING_USDC const autopilotTrader = useMemo( () => traders.find((trader) => trader.trader_name === 'NOFX Autopilot') || traders.find((trader) => (trader.strategy_name || '').toLowerCase().includes('claw402') ) || null, [traders] ) const allReady = feeReady && hyperliquidConnected && tradingBalanceReady const loadWallet = async () => { setWalletLoading(true) try { setWallet(await api.getCurrentBeginnerWallet()) } catch { setWallet(null) } finally { setWalletLoading(false) } } useEffect(() => { void loadWallet() }, []) const refreshEverything = async () => { setRefreshing(true) try { await Promise.all([onRefresh(), loadWallet()]) } finally { setRefreshing(false) } } const ensureClaw402Strategy = async () => { const strategies = await api.getStrategies() const existing = strategies.find( (strategy) => strategy.is_active && strategy.config?.ai_config?.coin_source?.source_type === 'vergex_signal' ) || strategies.find((strategy) => strategy.name.toLowerCase().includes('claw402') ) if (existing) { if (!existing.is_active) { await api.activateStrategy(existing.id) } return existing.id } const config = await api.getDefaultStrategyConfig() const created = await api.createStrategy({ name: 'NOFX Claw402 Auto Strategy', description: 'Single built-in strategy: Claw402 board, per-symbol details, raw candles, then execution.', config, }) if (created?.id) { await api.activateStrategy(created.id) return created.id } const refreshed = await api.getStrategies() const fallback = refreshed.find((strategy) => strategy.name.toLowerCase().includes('claw402') ) if (!fallback) throw new Error('Failed to create Claw402 strategy') await api.activateStrategy(fallback.id) return fallback.id } const launchAutopilot = async () => { if (!claw402Model || !hyperliquidExchange) return setLaunching(true) try { let trader = autopilotTrader if (!trader) { const strategyId = await ensureClaw402Strategy() trader = await api.createTrader({ name: 'NOFX Autopilot', ai_model_id: claw402Model.id, exchange_id: hyperliquidExchange.id, strategy_id: strategyId, scan_interval_minutes: 15, is_cross_margin: true, show_in_competition: true, btc_eth_leverage: 10, altcoin_leverage: 10, }) } if (!trader.is_running) { await api.startTrader(trader.trader_id) } await onRefresh() toast.success('NOFX Autopilot is running') navigate(buildDashboardPath(trader.trader_id)) } catch (err) { toast.error(err instanceof Error ? err.message : 'Launch failed') } finally { setLaunching(false) } } const steps: Array<{ title: string detail: string status: LaunchStepStatus meta?: string action?: JSX.Element }> = [ { title: 'AI fee wallet', detail: 'Pays Claw402.ai data and model calls with Base USDC. This is separate from trading collateral.', status: feeReady ? 'ready' : 'action', meta: feeWalletAddress ? `${shortAddress(feeWalletAddress)} · ${formatUSDC(feeWalletBalance)} USDC` : 'Base USDC wallet required', action: feeWalletAddress ? (
) : ( ), }, { title: 'Hyperliquid trading wallet', detail: 'Connect an EVM wallet, approve a NOFX Agent, approve the builder fee, then save it to NOFX.', status: hyperliquidConnected ? 'ready' : 'action', meta: hyperliquidExchange?.hyperliquidWalletAddr ? `${shortAddress(hyperliquidExchange.hyperliquidWalletAddr)} · authorized` : 'Agent and trading authorization required', action: ( ), }, { title: 'Trading balance', detail: 'Deposit USDC to Hyperliquid. NOFX uses it as margin for the Claw402 Autopilot strategy.', status: tradingBalanceReady ? 'ready' : hyperliquidConnected ? 'action' : 'blocked', meta: hyperliquidConnected ? `${formatUSDC(tradingBalance)} USDC available` : 'Connect Hyperliquid first', }, { title: 'NOFX Autopilot', detail: 'Reads the Claw402 board, fetches Signal Lab and liquidation structure, confirms with candles, then trades full-size 10x only when the setup is strong enough.', status: allReady ? 'ready' : 'blocked', meta: autopilotTrader?.is_running ? 'Running' : autopilotTrader ? 'Ready to start' : 'Ready to create when setup is complete', }, ] const renderPrimaryAction = () => { if (!feeReady) { return ( ) } if (!hyperliquidConnected) { return ( ) } if (!tradingBalanceReady) { return ( Deposit USDC on Hyperliquid ) } if (autopilotTrader?.is_running) { return ( ) } return ( ) } return (
Guided Launch

Start NOFX Autopilot in minutes

One strategy, one launch path. Fund the AI fee wallet, authorize Hyperliquid, deposit USDC, then run the Claw402 Autopilot.

{renderPrimaryAction()}
{steps.map((step, index) => (
{step.status === 'ready' ? ( ) : step.status === 'action' ? ( index + 1 ) : ( )}

{step.title}

{step.action}

{step.detail}

{step.meta}
))}
) }