nyxora 1.6.13 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -2
  3. package/bin/nyxora.mjs +8 -0
  4. package/package.json +1 -2
  5. package/packages/core/package.json +1 -1
  6. package/packages/core/src/agent/limitOrderManager.ts +2 -2
  7. package/packages/core/src/agent/reasoning.ts +36 -14
  8. package/packages/core/src/config/parser.ts +99 -9
  9. package/packages/core/src/gateway/cli.ts +28 -2
  10. package/packages/core/src/gateway/setup.ts +45 -10
  11. package/packages/core/src/gateway/telegram.ts +53 -12
  12. package/packages/core/src/system/skills/searchWeb.ts +187 -21
  13. package/packages/core/src/utils/formatter.ts +12 -5
  14. package/packages/core/src/web3/config.ts +7 -1
  15. package/packages/core/src/web3/skills/bridgeToken.ts +4 -3
  16. package/packages/core/src/web3/skills/checkAddress.ts +2 -2
  17. package/packages/core/src/web3/skills/checkPortfolio.ts +55 -39
  18. package/packages/core/src/web3/skills/checkSecurity.ts +3 -2
  19. package/packages/core/src/web3/skills/customTx.ts +2 -2
  20. package/packages/core/src/web3/skills/getBalance.ts +3 -3
  21. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -2
  22. package/packages/core/src/web3/skills/mintNft.ts +2 -2
  23. package/packages/core/src/web3/skills/swapToken.ts +4 -3
  24. package/packages/core/src/web3/skills/transfer.ts +2 -2
  25. package/packages/core/src/web3/utils/tokens.ts +8 -0
  26. package/packages/dashboard/dist/assets/{index-24OeXn-k.css → index-BSk4CLkG.css} +1 -1
  27. package/packages/dashboard/dist/assets/{index-BuYfTEKE.js → index-Dc3Tu0Te.js} +21 -21
  28. package/packages/dashboard/dist/index.html +2 -2
  29. package/packages/dashboard/package.json +1 -1
  30. package/packages/mcp-server/package.json +1 -1
  31. package/packages/policy/package.json +1 -1
  32. package/packages/signer/package.json +1 -1
  33. package/launcher.js +0 -48
  34. package/packages/core/src/agent/reasoning.d.ts.map +0 -1
  35. package/packages/core/src/config/parser.d.ts.map +0 -1
  36. package/packages/core/src/gateway/cli.d.ts.map +0 -1
  37. package/packages/core/src/memory/logger.d.ts.map +0 -1
  38. package/packages/core/src/utils/safeLogger.js +0 -59
  39. package/packages/core/src/web3/config.d.ts.map +0 -1
  40. package/packages/core/src/web3/skills/getBalance.d.ts.map +0 -1
  41. package/packages/dashboard/public/favicon.svg +0 -10
  42. package/packages/dashboard/public/icons.svg +0 -24
  43. package/packages/dashboard/src/App.css +0 -184
  44. package/packages/dashboard/src/App.tsx +0 -588
  45. package/packages/dashboard/src/BalanceWidget.tsx +0 -65
  46. package/packages/dashboard/src/MarketWidget.tsx +0 -73
  47. package/packages/dashboard/src/NetworkSelector.tsx +0 -64
  48. package/packages/dashboard/src/NyxoraLogo.tsx +0 -25
  49. package/packages/dashboard/src/OsSkills.tsx +0 -352
  50. package/packages/dashboard/src/Overview.tsx +0 -157
  51. package/packages/dashboard/src/PendingTransactions.tsx +0 -75
  52. package/packages/dashboard/src/Settings.tsx +0 -338
  53. package/packages/dashboard/src/Skills.tsx +0 -200
  54. package/packages/dashboard/src/SwapWidget.tsx +0 -141
  55. package/packages/dashboard/src/TransactionWidget.tsx +0 -95
  56. package/packages/dashboard/src/assets/hero.png +0 -0
  57. package/packages/dashboard/src/assets/react.svg +0 -1
  58. package/packages/dashboard/src/assets/vite.svg +0 -1
  59. package/packages/dashboard/src/components/PillSelect.tsx +0 -65
  60. package/packages/dashboard/src/index.css +0 -807
  61. package/packages/dashboard/src/main.tsx +0 -10
  62. package/packages/dashboard/src/overview.css +0 -304
  63. package/packages/dashboard/src/utils/api.ts +0 -31
  64. package/packages/mcp-server/tsconfig.tsbuildinfo +0 -1
  65. package/test-address.ts +0 -11
  66. package/test-all-chains.ts +0 -19
  67. package/test-db.ts +0 -3
  68. package/test-portfolio.ts +0 -14
  69. package/tsconfig.tsbuildinfo +0 -1
@@ -1,157 +0,0 @@
1
- import { apiFetch } from './utils/api';
2
- import React, { useState, useEffect, useRef } from 'react';
3
- import './overview.css';
4
-
5
- interface Config {
6
- agent: { name: string; default_chain: string };
7
- llm: { provider: string; model: string; temperature: number };
8
- }
9
-
10
- interface Stats {
11
- cost: number;
12
- tokens: number;
13
- messages: number;
14
- }
15
-
16
- interface EventLog {
17
- timestamp: string;
18
- event: string;
19
- meta: any;
20
- }
21
-
22
- interface GatewayLog {
23
- timestamp: string;
24
- message: string;
25
- meta?: any;
26
- }
27
-
28
- interface OverviewProps {
29
- config: Config | null;
30
- }
31
-
32
- const Overview: React.FC<OverviewProps> = ({ config }) => {
33
- const [stats, setStats] = useState<Stats>({ cost: 0, tokens: 0, messages: 0 });
34
- const [events, setEvents] = useState<EventLog[]>([]);
35
- const [gatewayLogs, setGatewayLogs] = useState<GatewayLog[]>([]);
36
- const eventLogsEndRef = useRef<HTMLDivElement>(null);
37
- const gatewayLogsEndRef = useRef<HTMLDivElement>(null);
38
-
39
- useEffect(() => {
40
- const fetchData = async () => {
41
- try {
42
- const statsRes = await apiFetch('/api/stats');
43
- if (statsRes.ok) setStats(await statsRes.json());
44
-
45
- const logsRes = await apiFetch('/api/logs');
46
- if (logsRes.ok) {
47
- const logs = await logsRes.json();
48
- setEvents(logs.events);
49
- setGatewayLogs(logs.gateway);
50
- }
51
- } catch (err) {
52
- console.error("Failed to fetch analytics");
53
- }
54
- };
55
-
56
- fetchData();
57
- const interval = setInterval(fetchData, 2000);
58
- return () => clearInterval(interval);
59
- }, []);
60
-
61
- if (!config) return <div className="overview-container">Loading...</div>;
62
-
63
- return (
64
- <div className="overview-container">
65
- <div className="overview-header">
66
- <h1>Nyxora Status</h1>
67
- <p>System health, active configuration, and loaded Web3 skills.</p>
68
- </div>
69
-
70
- <div className="panel gateway-access">
71
- <div className="panel-header">
72
- <h3>System Configuration</h3>
73
- <p>Current runtime parameters for your Web3 Agent.</p>
74
- </div>
75
-
76
- <div className="form-row">
77
- <div className="form-group flex-1">
78
- <label>API Endpoint</label>
79
- <input type="text" value="/api/chat" readOnly />
80
- </div>
81
- <div className="form-group flex-1">
82
- <label>Agent Name</label>
83
- <input type="text" value={config.agent.name} readOnly />
84
- </div>
85
- <div className="form-group flex-1">
86
- <label>Memory Storage</label>
87
- <input type="text" value="./memory.json" readOnly />
88
- </div>
89
- </div>
90
- </div>
91
-
92
- <div className="metrics-grid">
93
- <div className="metric-card">
94
- <label>COST</label>
95
- <div className="metric-val">${stats.cost.toFixed(4)}</div>
96
- <div className="metric-sub">{stats.tokens} tokens - {stats.messages} msgs</div>
97
- </div>
98
- <div className="metric-card">
99
- <label>SESSIONS</label>
100
- <div className="metric-val">1</div>
101
- <div className="metric-sub">Local chat session active</div>
102
- </div>
103
- <div className="metric-card">
104
- <label>SKILLS</label>
105
- <div className="metric-val">2/2</div>
106
- <div className="metric-sub">2 Web3 skills loaded</div>
107
- </div>
108
- <div className="metric-card">
109
- <label>CRON</label>
110
- <div className="metric-val">0 jobs</div>
111
- <div className="metric-sub">No scheduled tasks</div>
112
- </div>
113
- <div className="metric-card">
114
- <label>MODEL AUTH</label>
115
- <div className="metric-val text-green">1 ok</div>
116
- <div className="metric-sub">{config.llm.provider.toUpperCase()} provider connected</div>
117
- </div>
118
- </div>
119
-
120
- <div className="logs-grid">
121
- <div className="log-panel">
122
- <div className="log-header">
123
- <span>Event Log <span className="badge">{events.length}</span></span>
124
- </div>
125
- <div className="log-content">
126
- {events.map((log, i) => (
127
- <div key={i} className="log-row">
128
- <span className="log-time">{log.timestamp}</span>
129
- <span className="log-msg">
130
- {log.event} <span className="log-meta">{JSON.stringify(log.meta)}</span>
131
- </span>
132
- </div>
133
- ))}
134
- <div ref={eventLogsEndRef} />
135
- </div>
136
- </div>
137
- <div className="log-panel">
138
- <div className="log-header">
139
- <span>Gateway Logs <span className="badge">{gatewayLogs.length}</span></span>
140
- </div>
141
- <div className="log-content">
142
- {gatewayLogs.map((log, i) => (
143
- <div key={i} className="log-row gateway-row">
144
- <span className="log-json">
145
- {`{"timestamp":"${log.timestamp}","message":${JSON.stringify(log.message)}${log.meta ? `,"meta":${JSON.stringify(log.meta)}` : ''}}`}
146
- </span>
147
- </div>
148
- ))}
149
- <div ref={gatewayLogsEndRef} />
150
- </div>
151
- </div>
152
- </div>
153
- </div>
154
- );
155
- };
156
-
157
- export default Overview;
@@ -1,75 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { apiFetch } from './utils/api';
3
- import { ShieldAlert, Check, X } from 'lucide-react';
4
-
5
- export default function PendingTransactions() {
6
- const [pending, setPending] = useState<any[]>([]);
7
- const [loadingId, setLoadingId] = useState<string | null>(null);
8
-
9
- useEffect(() => {
10
- const fetchPending = async () => {
11
- try {
12
- const res = await apiFetch('/api/transactions');
13
- if (res.ok) {
14
- setPending(await res.json());
15
- }
16
- } catch (e) {
17
- console.error(e);
18
- }
19
- };
20
- fetchPending();
21
- const interval = setInterval(fetchPending, 2000);
22
- return () => clearInterval(interval);
23
- }, []);
24
-
25
- const handleAction = async (id: string, action: 'approve' | 'reject') => {
26
- setLoadingId(id);
27
- try {
28
- const res = await apiFetch(`/api/transactions/${id}/${action}`, { method: 'POST' });
29
- if (res.ok) {
30
- setPending(prev => prev.filter(t => t.id !== id));
31
- } else {
32
- const data = await res.json();
33
- alert(`Error: ${data.error}`);
34
- }
35
- } catch (e: any) {
36
- alert(`Failed to ${action}: ${e.message}`);
37
- } finally {
38
- setLoadingId(null);
39
- }
40
- };
41
-
42
- if (pending.length === 0) return null;
43
-
44
- return (
45
- <div style={{ position: 'fixed', bottom: 20, right: 20, width: 350, zIndex: 1000, display: 'flex', flexDirection: 'column', gap: 10 }}>
46
- {pending.map(tx => (
47
- <div key={tx.id} style={{ background: '#1e293b', padding: 20, borderRadius: 16, border: '1px solid #eab308', boxShadow: '0 10px 25px -5px rgba(0,0,0,0.5)' }}>
48
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 15 }}>
49
- <ShieldAlert color="#eab308" />
50
- <strong style={{ color: 'white' }}>Action Required</strong>
51
- </div>
52
- <p style={{ color: '#cbd5e1', fontSize: '0.9rem', marginBottom: 15 }}>
53
- Type: {tx.type.toUpperCase()}<br/>
54
- Chain: {tx.chainName.toUpperCase()}<br/>
55
- {tx.type === 'transfer' ? `To: ${tx.details.toAddress}\nAmount: ${tx.details.amountEth}` : `Swap: ${tx.details.amount} ${tx.details.fromToken} to ${tx.details.toToken}`}
56
- </p>
57
- <div style={{ display: 'flex', gap: 10 }}>
58
- <button
59
- onClick={() => handleAction(tx.id, 'approve')}
60
- disabled={loadingId === tx.id}
61
- style={{ flex: 1, padding: '10px', background: loadingId === tx.id ? '#15803d' : '#22c55e', color: 'white', border: 'none', borderRadius: 8, cursor: loadingId === tx.id ? 'not-allowed' : 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 5 }}>
62
- <Check size={16} /> {loadingId === tx.id ? 'Processing...' : 'Approve'}
63
- </button>
64
- <button
65
- onClick={() => handleAction(tx.id, 'reject')}
66
- disabled={loadingId === tx.id}
67
- style={{ flex: 1, padding: '10px', background: loadingId === tx.id ? '#b91c1c' : '#ef4444', color: 'white', border: 'none', borderRadius: 8, cursor: loadingId === tx.id ? 'not-allowed' : 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 5 }}>
68
- <X size={16} /> Reject
69
- </button>
70
- </div>
71
- </div>
72
- ))}
73
- </div>
74
- );
75
- }
@@ -1,338 +0,0 @@
1
- import { apiFetch } from './utils/api';
2
- import React, { useState, useEffect } from 'react';
3
- import { Save, User, Cpu, Key, Network, Globe } from 'lucide-react';
4
- import { PillSelect } from './components/PillSelect';
5
-
6
- interface Config {
7
- agent: { name: string; default_chain: string };
8
- llm: { provider: string; model: string; temperature: number; api_keys?: string[] };
9
- web3?: { rpc_urls?: Record<string, string | string[]> };
10
- }
11
-
12
- interface SettingsProps {
13
- config: Config | null;
14
- onConfigChange: (newConfig: Config) => void;
15
- }
16
-
17
- const Settings: React.FC<SettingsProps> = ({ config, onConfigChange }) => {
18
- const [formData, setFormData] = useState<Config | null>(null);
19
- const [isSaving, setIsSaving] = useState(false);
20
-
21
- useEffect(() => {
22
- if (config) {
23
- setFormData({
24
- agent: {
25
- name: config.agent?.name || 'Nyxora',
26
- default_chain: config.agent?.default_chain || 'base'
27
- },
28
- llm: {
29
- provider: config.llm?.provider || 'openai',
30
- model: config.llm?.model || 'gpt-4o-mini',
31
- temperature: config.llm?.temperature ?? 0.2,
32
- api_keys: Array.isArray(config.llm?.api_keys)
33
- ? config.llm.api_keys
34
- : (config.llm?.api_keys ? [config.llm.api_keys as unknown as string] : [])
35
- },
36
- web3: {
37
- rpc_urls: config.web3?.rpc_urls || {}
38
- }
39
- });
40
- }
41
- }, [config]);
42
-
43
- if (!formData) return <div className="overview-container">Loading settings...</div>;
44
-
45
- const handleChange = (section: 'agent' | 'llm', field: string, value: string | number) => {
46
- setFormData(prev => {
47
- if (!prev) return prev;
48
- return {
49
- ...prev,
50
- [section]: {
51
- ...prev[section],
52
- [field]: field === 'temperature' ? Number(value) : value
53
- }
54
- };
55
- });
56
- };
57
-
58
- const handleWeb3Change = (chainName: string, value: string) => {
59
- setFormData(prev => {
60
- if (!prev) return prev;
61
- const urls = value.split(',').map(s => s.trim()).filter(s => s);
62
- const newRpcUrls = { ...(prev.web3?.rpc_urls || {}) };
63
-
64
- if (urls.length === 0) {
65
- delete newRpcUrls[chainName];
66
- } else if (urls.length === 1) {
67
- newRpcUrls[chainName] = urls[0];
68
- } else {
69
- newRpcUrls[chainName] = urls;
70
- }
71
-
72
- return {
73
- ...prev,
74
- web3: {
75
- ...prev.web3,
76
- rpc_urls: newRpcUrls
77
- }
78
- };
79
- });
80
- };
81
-
82
- const handleAddApiKey = () => {
83
- setFormData(prev => {
84
- if (!prev) return prev;
85
- const currentKeys = prev.llm.api_keys || [];
86
- if (currentKeys.length >= 10) return prev;
87
- return { ...prev, llm: { ...prev.llm, api_keys: [...currentKeys, ''] } };
88
- });
89
- };
90
-
91
- const handleUpdateApiKey = (index: number, value: string) => {
92
- setFormData(prev => {
93
- if (!prev) return prev;
94
- const newKeys = [...(prev.llm.api_keys || [])];
95
- newKeys[index] = value;
96
- return { ...prev, llm: { ...prev.llm, api_keys: newKeys } };
97
- });
98
- };
99
-
100
- const handleRemoveApiKey = (index: number) => {
101
- setFormData(prev => {
102
- if (!prev) return prev;
103
- const newKeys = [...(prev.llm.api_keys || [])];
104
- newKeys.splice(index, 1);
105
- return { ...prev, llm: { ...prev.llm, api_keys: newKeys } };
106
- });
107
- };
108
-
109
- const handleSave = async () => {
110
- setIsSaving(true);
111
- try {
112
- const res = await apiFetch('/api/config', {
113
- method: 'POST',
114
- headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify(formData)
116
- });
117
- if (res.ok) {
118
- onConfigChange(formData);
119
- alert('Settings saved successfully!');
120
- }
121
- } catch (e) {
122
- console.error(e);
123
- alert('Failed to save settings');
124
- } finally {
125
- setIsSaving(false);
126
- }
127
- };
128
-
129
- return (
130
- <div className="overview-container" style={{ maxWidth: '900px', margin: '0 auto' }}>
131
- <div className="overview-header" style={{ marginBottom: '32px' }}>
132
- <h1 style={{ color: '#eceff4' }}>Configuration</h1>
133
- <p style={{ color: '#d8dee9' }}>Modify the core behaviors and parameters of the Nyxora Agent.</p>
134
- </div>
135
-
136
- <div className="panel" style={{ background: 'transparent', border: 'none', padding: 0 }}>
137
- <div className="nord-panel-header">
138
- <User size={18} color="#81a1c1" />
139
- <h3>Agent Profile</h3>
140
- </div>
141
- <div className="form-row">
142
- <div className="form-group flex-1">
143
- <label className="nord-label">Agent Name</label>
144
- <input
145
- className="nord-pill-input"
146
- type="text"
147
- value={formData.agent.name}
148
- onChange={e => handleChange('agent', 'name', e.target.value)}
149
- />
150
- </div>
151
- <div className="form-group flex-1">
152
- <label className="nord-label">Default Web3 Chain</label>
153
- <PillSelect
154
- value={formData.agent.default_chain}
155
- onChange={(val) => handleChange('agent', 'default_chain', val)}
156
- pillColor="#88c0d0"
157
- textColor="#000000"
158
- options={[
159
- { id: 'ethereum', label: 'Ethereum Mainnet', icon: <Globe size={14} /> },
160
- { id: 'bsc', label: 'BNB Chain', icon: <Globe size={14} /> },
161
- { id: 'base', label: 'Base', icon: <Globe size={14} /> },
162
- { id: 'optimism', label: 'Optimism', icon: <Globe size={14} /> },
163
- { id: 'arbitrum', label: 'Arbitrum', icon: <Globe size={14} /> },
164
- { id: 'sepolia', label: 'Sepolia Testnet', icon: <Globe size={14} /> }
165
- ]}
166
- />
167
- </div>
168
- </div>
169
- </div>
170
-
171
- <div className="panel" style={{ background: 'transparent', border: 'none', padding: 0, marginTop: '32px' }}>
172
- <div className="nord-panel-header">
173
- <Cpu size={18} color="#81a1c1" />
174
- <h3>LLM Engine</h3>
175
- </div>
176
- <div className="form-row">
177
- <div className="form-group flex-1">
178
- <label className="nord-label">Provider</label>
179
- <PillSelect
180
- value={formData.llm.provider}
181
- onChange={(val) => handleChange('llm', 'provider', val)}
182
- pillColor="#81a1c1"
183
- textColor="#000000"
184
- options={[
185
- { id: 'gemini', label: 'Google Gemini', icon: <Cpu size={14} /> },
186
- { id: 'openai', label: 'OpenAI', icon: <Cpu size={14} /> },
187
- { id: 'openrouter', label: 'OpenRouter', icon: <Cpu size={14} /> },
188
- { id: 'ollama', label: 'Ollama (Local)', icon: <Cpu size={14} /> }
189
- ]}
190
- />
191
- </div>
192
- <div className="form-group flex-1">
193
- <label className="nord-label">Model Name</label>
194
- <input
195
- className="nord-pill-input"
196
- style={{ color: '#81a1c1' }}
197
- type="text"
198
- value={formData.llm.model}
199
- onChange={e => handleChange('llm', 'model', e.target.value)}
200
- />
201
- </div>
202
- <div className="form-group flex-1">
203
- <label className="nord-label">Temperature ({formData.llm.temperature})</label>
204
- <input
205
- className="nord-slider"
206
- type="range"
207
- min="0" max="1" step="0.1"
208
- value={formData.llm.temperature}
209
- onChange={e => handleChange('llm', 'temperature', e.target.value)}
210
- />
211
- </div>
212
- </div>
213
- </div>
214
-
215
- <div className="panel" style={{ background: 'transparent', border: 'none', padding: 0, marginTop: '32px' }}>
216
- <div className="nord-panel-header" style={{ justifyContent: 'space-between' }}>
217
- <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
218
- <Key size={18} color="#81a1c1" />
219
- <h3>API Keys (Rotation)</h3>
220
- </div>
221
- <span style={{ fontSize: '0.8rem', color: '#81a1c1', fontWeight: 600 }}>
222
- {formData.llm.api_keys?.length || 0} / 10 Keys
223
- </span>
224
- </div>
225
- <p style={{ fontSize: '0.85rem', color: '#d8dee9', marginBottom: '20px' }}>
226
- Add up to 10 API keys. The system will automatically rotate through them (Round-Robin) for each request to prevent rate limits. Leave empty to fallback to default credentials set via CLI (nyxora setup).
227
- </p>
228
-
229
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
230
- {(formData.llm.api_keys || []).map((key, index) => (
231
- <div key={index} style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
232
- <input
233
- className="nord-input"
234
- type="password"
235
- value={key}
236
- placeholder="sk-..."
237
- onChange={(e) => handleUpdateApiKey(index, e.target.value)}
238
- />
239
- <button
240
- onClick={() => handleRemoveApiKey(index)}
241
- style={{
242
- background: 'rgba(191, 97, 106, 0.15)',
243
- color: '#bf616a',
244
- border: '1px solid rgba(191, 97, 106, 0.3)',
245
- borderRadius: '6px',
246
- padding: '10px 16px',
247
- cursor: 'pointer',
248
- transition: 'all 0.2s',
249
- fontWeight: 600,
250
- fontSize: '0.85rem'
251
- }}
252
- onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(191, 97, 106, 0.25)'}
253
- onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(191, 97, 106, 0.15)'}
254
- >
255
- Delete
256
- </button>
257
- </div>
258
- ))}
259
-
260
- {(!formData.llm.api_keys || formData.llm.api_keys.length < 10) && (
261
- <button
262
- onClick={handleAddApiKey}
263
- style={{
264
- alignSelf: 'flex-start',
265
- background: 'transparent',
266
- color: '#81a1c1',
267
- border: '1px dashed #434c5e',
268
- borderRadius: '6px',
269
- padding: '10px 20px',
270
- cursor: 'pointer',
271
- marginTop: '8px',
272
- transition: 'all 0.2s',
273
- fontWeight: 600,
274
- fontSize: '0.85rem'
275
- }}
276
- onMouseEnter={(e) => {
277
- e.currentTarget.style.borderColor = '#81a1c1';
278
- e.currentTarget.style.background = 'rgba(129, 161, 193, 0.1)';
279
- }}
280
- onMouseLeave={(e) => {
281
- e.currentTarget.style.borderColor = '#434c5e';
282
- e.currentTarget.style.background = 'transparent';
283
- }}
284
- >
285
- + Add API Key
286
- </button>
287
- )}
288
- </div>
289
- </div>
290
-
291
- <div className="panel" style={{ background: 'transparent', border: 'none', padding: 0, marginTop: '40px' }}>
292
- <div className="nord-panel-header">
293
- <Network size={18} color="#81a1c1" />
294
- <h3>Web3 & RPC Settings</h3>
295
- </div>
296
- <p style={{ fontSize: '0.85rem', color: '#d8dee9', marginBottom: '20px' }}>
297
- Override the default public RPCs with your own Premium endpoints (Alchemy/Infura).
298
- Separate multiple URLs with a comma for Fallback High-Availability.
299
- </p>
300
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
301
- {['ethereum', 'base', 'bsc', 'arbitrum', 'optimism', 'sepolia'].map(chain => {
302
- const rpcVal = formData.web3?.rpc_urls?.[chain];
303
- const displayVal = Array.isArray(rpcVal) ? rpcVal.join(', ') : (rpcVal || '');
304
- return (
305
- <div key={chain} className="form-group" style={{ position: 'relative' }}>
306
- <label className="nord-label" style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
307
- <img
308
- src={`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${chain === 'bsc' ? 'smartchain' : chain}/info/logo.png`}
309
- alt={chain}
310
- style={{ width: '14px', height: '14px', borderRadius: '50%' }}
311
- onError={(e) => e.currentTarget.style.display = 'none'}
312
- />
313
- {chain} RPC
314
- </label>
315
- <input
316
- className="nord-input"
317
- type="text"
318
- placeholder="https://..."
319
- value={displayVal}
320
- onChange={(e) => handleWeb3Change(chain, e.target.value)}
321
- />
322
- </div>
323
- );
324
- })}
325
- </div>
326
- </div>
327
-
328
- <div className="form-actions" style={{ justifyContent: 'flex-end', marginTop: '48px', paddingTop: '24px', borderTop: '1px solid rgba(216, 222, 233, 0.05)' }}>
329
- <button className="nord-btn-primary" onClick={handleSave} disabled={isSaving}>
330
- <Save size={16} style={{ marginRight: '8px' }} />
331
- {isSaving ? 'Saving...' : 'Save Configuration'}
332
- </button>
333
- </div>
334
- </div>
335
- );
336
- };
337
-
338
- export default Settings;