File size: 6,637 Bytes
945e815
5f5514e
945e815
 
5f5514e
 
945e815
5f5514e
 
383d246
945e815
383d246
5f5514e
 
 
 
945e815
5f5514e
 
 
 
 
 
 
 
 
945e815
 
5f5514e
 
945e815
5f5514e
 
 
 
945e815
5f5514e
 
 
 
 
 
 
 
 
 
945e815
 
 
 
 
 
5f5514e
 
 
 
 
 
 
 
383d246
5f5514e
 
383d246
5f5514e
 
 
945e815
5f5514e
 
945e815
5f5514e
 
 
 
945e815
 
 
 
 
 
5f5514e
 
383d246
5f5514e
 
383d246
945e815
5f5514e
 
 
945e815
 
5f5514e
383d246
5f5514e
945e815
5f5514e
 
945e815
5f5514e
 
945e815
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React, { useState, useEffect } from 'react';
import { useToast } from '../components/ui/index';

export default function SecurityPage() {
  const [s, setS] = useState<any>({});
  const [wl, setWl] = useState<any[]>([]);
  const [anomalies, setAnomalies] = useState<any[]>([]);
  const [addr, setAddr] = useState(''); const [label, setLabel] = useState('');
  const [err, setErr] = useState('');
  const { addToast } = useToast();

  useEffect(() => { load(); }, []);
  const load = async () => { if (!window.solvox) return; try { const [a, b, c] = await Promise.all([window.solvox.security.getSettings(), window.solvox.security.getWhitelist(), window.solvox.security.getAnomalies()]); setS(a); setWl(b); setAnomalies(c); } catch {} };
  const upd = async (k: string, v: any) => { const n = { ...s, [k]: v }; setS(n); if (window.solvox) { await window.solvox.security.updateSettings(n); addToast({ type: 'success', title: 'Saved' }); } };
  const addWl = async () => { if (!addr.trim() || !label.trim()) return setErr('Both required'); if (window.solvox) { const r = await window.solvox.security.addWhitelist(addr.trim(), label.trim()); if (r.success) { setAddr(''); setLabel(''); setErr(''); load(); } else setErr(r.error || 'Failed'); } };
  const rmWl = async (a: string) => { if (window.solvox) { await window.solvox.security.removeWhitelist(a); load(); } };

  const Toggle = ({ k, label, desc }: { k: string; label: string; desc: string }) => (
    <div className="flex items-center justify-between py-3 border-b border-hairline-soft last:border-0">
      <div><div className="text-title-sm text-ink">{label}</div><div className="text-caption text-muted">{desc}</div></div>
      <button onClick={() => upd(k, !s[k])} className="toggle">
        <div className={`toggle-track ${s[k] ? 'active' : ''}`} />
        <div className={`toggle-thumb ${s[k] ? 'active' : ''}`} />
      </button>
    </div>
  );

  return (
    <div className="max-w-3xl mx-auto px-8 py-section">
      <h2 className="display-text text-title-lg text-ink mb-8">Security</h2>

      {/* Limits */}
      <div className="card mb-6">
        <div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Transaction limits</div>
        <div className="grid grid-cols-2 gap-4">
          {[
            { k: 'maxSingleTx', l: 'Max per transaction', u: 'tokens', v: s.maxSingleTx || 1000 },
            { k: 'maxDailyVolume', l: 'Daily limit', u: '/day', v: s.maxDailyVolume || 5000 },
            { k: 'velocityLimit', l: 'Max TX/hour', u: '', v: s.velocityLimit || 10 },
            { k: 'cooldownMinutes', l: 'Cooldown', u: 'min', v: s.cooldownMinutes || 1 },
          ].map(f => (
            <div key={f.k}>
              <label className="text-caption text-muted block mb-1">{f.l}</label>
              <div className="flex items-center gap-2">
                <input type="number" value={f.v} onChange={e => upd(f.k, Number(e.target.value))} className="input-field text-body-sm" />
                {f.u && <span className="text-caption text-muted whitespace-nowrap">{f.u}</span>}
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Toggles */}
      <div className="card mb-6">
        <div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Features</div>
        <Toggle k="whitelistEnabled" label="Address whitelisting" desc="Only allow sends to approved addresses" />
        <Toggle k="anomalyDetection" label="AI anomaly detection" desc="LLM analyzes spending patterns locally" />
        <Toggle k="requireConfirmation" label="Transaction confirmation" desc="Always require explicit approval" />
      </div>

      {/* Whitelist */}
      <div className="card mb-6">
        <div className="text-caption-strong text-muted uppercase tracking-wider mb-3">Address whitelist</div>
        <div className="flex gap-2 mb-3">
          <input value={label} onChange={e => setLabel(e.target.value)} placeholder="Label" className="input-field text-body-sm w-28" />
          <input value={addr} onChange={e => setAddr(e.target.value)} placeholder="Solana address" className="input-field text-body-sm flex-1 font-mono" />
          <button onClick={addWl} className="btn-primary text-body-sm py-2 px-4">Add</button>
        </div>
        {err && <div className="text-caption text-semantic-down mb-2">{err}</div>}
        {wl.length === 0 ? <div className="text-body-sm text-muted text-center py-4">No addresses whitelisted</div> : (
          <div className="space-y-2">
            {wl.map((e, i) => (
              <div key={i} className="flex items-center justify-between bg-surface-soft rounded-lg p-3">
                <div><div className="text-title-sm text-ink">{e.label}</div><div className="text-caption font-mono text-muted">{e.address.slice(0, 10)}…{e.address.slice(-6)}</div></div>
                <button onClick={() => rmWl(e.address)} className="btn-text text-semantic-down text-body-sm">Remove</button>
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Anomalies */}
      <div className="card">
        <div className="flex items-center justify-between mb-3">
          <div className="text-caption-strong text-muted uppercase tracking-wider">Anomaly log</div>
          <span className="badge-pill-blue badge-pill text-[10px]">AI-POWERED</span>
        </div>
        {anomalies.length === 0 ? (
          <div className="text-center py-6">
            <div className="text-body-sm text-semantic-up">All clear — no anomalies detected ✓</div>
            <div className="text-caption text-muted mt-1">Analyzed by @qvac/llm-llamacpp + @qvac/embed-llamacpp</div>
          </div>
        ) : (
          <div className="space-y-2">
            {anomalies.map((a, i) => (
              <div key={i} className={`rounded-lg p-3 border ${a.severity === 'high' ? 'bg-semantic-down/5 border-semantic-down/15' : a.severity === 'medium' ? 'bg-accent-yellow/5 border-accent-yellow/15' : 'bg-surface-soft border-hairline'}`}>
                <div className="flex items-center gap-2 mb-1">
                  <span className={`badge-pill text-[10px] ${a.severity === 'high' ? 'badge-pill-red' : ''}`}>{a.severity.toUpperCase()}</span>
                  <span className="text-caption text-muted">{a.type}</span>
                </div>
                <div className="text-body-sm text-ink">{a.description}</div>
                <div className="text-caption text-muted-soft mt-1">{new Date(a.timestamp).toLocaleString()}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}