@thehoneyjar/sigil-hud 0.1.0
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.
- package/LICENSE.md +660 -0
- package/README.md +146 -0
- package/dist/index.d.ts +911 -0
- package/dist/index.js +3079 -0
- package/package.json +68 -0
- package/src/components/DataSourceIndicator.tsx +185 -0
- package/src/components/DiagnosticsPanel.tsx +444 -0
- package/src/components/FeedbackPrompt.tsx +348 -0
- package/src/components/HudPanel.tsx +179 -0
- package/src/components/HudTrigger.tsx +81 -0
- package/src/components/IssueList.tsx +228 -0
- package/src/components/LensPanel.tsx +286 -0
- package/src/components/ObservationCaptureModal.tsx +502 -0
- package/src/components/PhysicsAnalysis.tsx +273 -0
- package/src/components/SimulationPanel.tsx +173 -0
- package/src/components/StateComparison.tsx +238 -0
- package/src/hooks/useDataSource.ts +324 -0
- package/src/hooks/useKeyboardShortcuts.ts +125 -0
- package/src/hooks/useObservationCapture.ts +154 -0
- package/src/hooks/useSignalCapture.ts +138 -0
- package/src/index.ts +112 -0
- package/src/providers/HudProvider.tsx +115 -0
- package/src/store.ts +60 -0
- package/src/styles/theme.ts +256 -0
- package/src/types.ts +276 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physics Analysis Component
|
|
3
|
+
*
|
|
4
|
+
* Displays detected effect type and physics values for the current context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EffectType, ComplianceResult } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for PhysicsAnalysis
|
|
11
|
+
*/
|
|
12
|
+
export interface PhysicsAnalysisProps {
|
|
13
|
+
/** Detected effect type */
|
|
14
|
+
effect: EffectType | null
|
|
15
|
+
/** Physics compliance result */
|
|
16
|
+
compliance?: ComplianceResult | null
|
|
17
|
+
/** Whether analysis is loading */
|
|
18
|
+
isLoading?: boolean
|
|
19
|
+
/** Custom class name */
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Display label for effect types
|
|
25
|
+
*/
|
|
26
|
+
const effectLabels: Record<EffectType, string> = {
|
|
27
|
+
financial: 'Financial',
|
|
28
|
+
destructive: 'Destructive',
|
|
29
|
+
'soft-delete': 'Soft Delete',
|
|
30
|
+
standard: 'Standard',
|
|
31
|
+
local: 'Local State',
|
|
32
|
+
navigation: 'Navigation',
|
|
33
|
+
query: 'Query',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Color for effect types
|
|
38
|
+
*/
|
|
39
|
+
const effectColors: Record<EffectType, string> = {
|
|
40
|
+
financial: '#ef4444', // red
|
|
41
|
+
destructive: '#f97316', // orange
|
|
42
|
+
'soft-delete': '#eab308', // yellow
|
|
43
|
+
standard: '#22c55e', // green
|
|
44
|
+
local: '#3b82f6', // blue
|
|
45
|
+
navigation: '#8b5cf6', // purple
|
|
46
|
+
query: '#06b6d4', // cyan
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Physics Analysis component
|
|
51
|
+
*/
|
|
52
|
+
export function PhysicsAnalysis({
|
|
53
|
+
effect,
|
|
54
|
+
compliance,
|
|
55
|
+
isLoading = false,
|
|
56
|
+
className = '',
|
|
57
|
+
}: PhysicsAnalysisProps) {
|
|
58
|
+
if (isLoading) {
|
|
59
|
+
return (
|
|
60
|
+
<div className={className} style={styles.container}>
|
|
61
|
+
<div style={styles.header}>
|
|
62
|
+
<span style={styles.title}>Physics</span>
|
|
63
|
+
<span style={styles.loading}>Analyzing...</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!effect) {
|
|
70
|
+
return (
|
|
71
|
+
<div className={className} style={styles.container}>
|
|
72
|
+
<div style={styles.header}>
|
|
73
|
+
<span style={styles.title}>Physics</span>
|
|
74
|
+
</div>
|
|
75
|
+
<div style={styles.empty}>
|
|
76
|
+
No component selected. Select a component to view physics analysis.
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const color = effectColors[effect]
|
|
83
|
+
const behavioral = compliance?.behavioral
|
|
84
|
+
const animation = compliance?.animation
|
|
85
|
+
const material = compliance?.material
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className={className} style={styles.container}>
|
|
89
|
+
{/* Effect Badge */}
|
|
90
|
+
<div style={styles.effectRow}>
|
|
91
|
+
<span
|
|
92
|
+
style={{
|
|
93
|
+
...styles.effectBadge,
|
|
94
|
+
backgroundColor: `${color}20`,
|
|
95
|
+
borderColor: `${color}50`,
|
|
96
|
+
color,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
{effectLabels[effect]}
|
|
100
|
+
</span>
|
|
101
|
+
{behavioral && !behavioral.compliant && (
|
|
102
|
+
<span style={styles.warningBadge}>⚠ Non-compliant</span>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Physics Grid */}
|
|
107
|
+
{compliance && (
|
|
108
|
+
<div style={styles.grid}>
|
|
109
|
+
{/* Behavioral */}
|
|
110
|
+
<div style={styles.section}>
|
|
111
|
+
<span style={styles.sectionLabel}>Behavioral</span>
|
|
112
|
+
<div style={styles.values}>
|
|
113
|
+
<span style={styles.value}>
|
|
114
|
+
<span style={styles.valueLabel}>Sync:</span>
|
|
115
|
+
<span style={getValueStyle(behavioral?.compliant)}>
|
|
116
|
+
{behavioral?.sync ?? 'unknown'}
|
|
117
|
+
</span>
|
|
118
|
+
</span>
|
|
119
|
+
<span style={styles.value}>
|
|
120
|
+
<span style={styles.valueLabel}>Timing:</span>
|
|
121
|
+
<span style={getValueStyle(behavioral?.compliant)}>
|
|
122
|
+
{behavioral?.timing ? `${behavioral.timing}ms` : 'unknown'}
|
|
123
|
+
</span>
|
|
124
|
+
</span>
|
|
125
|
+
<span style={styles.value}>
|
|
126
|
+
<span style={styles.valueLabel}>Confirm:</span>
|
|
127
|
+
<span style={getValueStyle(behavioral?.compliant)}>
|
|
128
|
+
{behavioral?.confirmation ? 'yes' : 'no'}
|
|
129
|
+
</span>
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* Animation */}
|
|
135
|
+
<div style={styles.section}>
|
|
136
|
+
<span style={styles.sectionLabel}>Animation</span>
|
|
137
|
+
<div style={styles.values}>
|
|
138
|
+
<span style={styles.value}>
|
|
139
|
+
<span style={styles.valueLabel}>Easing:</span>
|
|
140
|
+
<span style={getValueStyle(animation?.compliant)}>
|
|
141
|
+
{animation?.easing ?? 'unknown'}
|
|
142
|
+
</span>
|
|
143
|
+
</span>
|
|
144
|
+
<span style={styles.value}>
|
|
145
|
+
<span style={styles.valueLabel}>Duration:</span>
|
|
146
|
+
<span style={getValueStyle(animation?.compliant)}>
|
|
147
|
+
{animation?.duration ? `${animation.duration}ms` : 'unknown'}
|
|
148
|
+
</span>
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Material */}
|
|
154
|
+
<div style={styles.section}>
|
|
155
|
+
<span style={styles.sectionLabel}>Material</span>
|
|
156
|
+
<div style={styles.values}>
|
|
157
|
+
<span style={styles.value}>
|
|
158
|
+
<span style={styles.valueLabel}>Surface:</span>
|
|
159
|
+
<span style={getValueStyle(material?.compliant)}>
|
|
160
|
+
{material?.surface ?? 'unknown'}
|
|
161
|
+
</span>
|
|
162
|
+
</span>
|
|
163
|
+
<span style={styles.value}>
|
|
164
|
+
<span style={styles.valueLabel}>Shadow:</span>
|
|
165
|
+
<span style={getValueStyle(material?.compliant)}>
|
|
166
|
+
{material?.shadow ?? 'unknown'}
|
|
167
|
+
</span>
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get value text style based on compliance
|
|
179
|
+
*/
|
|
180
|
+
function getValueStyle(compliant?: boolean): React.CSSProperties {
|
|
181
|
+
if (compliant === undefined) return styles.valueText
|
|
182
|
+
return compliant
|
|
183
|
+
? { ...styles.valueText, color: '#22c55e' }
|
|
184
|
+
: { ...styles.valueText, color: '#ef4444' }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Styles
|
|
189
|
+
*/
|
|
190
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
191
|
+
container: {
|
|
192
|
+
padding: '12px',
|
|
193
|
+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
194
|
+
borderRadius: '8px',
|
|
195
|
+
border: '1px solid rgba(255, 255, 255, 0.05)',
|
|
196
|
+
},
|
|
197
|
+
header: {
|
|
198
|
+
display: 'flex',
|
|
199
|
+
justifyContent: 'space-between',
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
marginBottom: '8px',
|
|
202
|
+
},
|
|
203
|
+
title: {
|
|
204
|
+
fontSize: '11px',
|
|
205
|
+
fontWeight: 600,
|
|
206
|
+
color: '#888',
|
|
207
|
+
textTransform: 'uppercase',
|
|
208
|
+
letterSpacing: '0.5px',
|
|
209
|
+
},
|
|
210
|
+
loading: {
|
|
211
|
+
fontSize: '10px',
|
|
212
|
+
color: '#666',
|
|
213
|
+
},
|
|
214
|
+
empty: {
|
|
215
|
+
fontSize: '11px',
|
|
216
|
+
color: '#666',
|
|
217
|
+
fontStyle: 'italic',
|
|
218
|
+
},
|
|
219
|
+
effectRow: {
|
|
220
|
+
display: 'flex',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
gap: '8px',
|
|
223
|
+
marginBottom: '12px',
|
|
224
|
+
},
|
|
225
|
+
effectBadge: {
|
|
226
|
+
padding: '4px 8px',
|
|
227
|
+
borderRadius: '4px',
|
|
228
|
+
fontSize: '11px',
|
|
229
|
+
fontWeight: 600,
|
|
230
|
+
border: '1px solid',
|
|
231
|
+
},
|
|
232
|
+
warningBadge: {
|
|
233
|
+
padding: '2px 6px',
|
|
234
|
+
borderRadius: '4px',
|
|
235
|
+
fontSize: '10px',
|
|
236
|
+
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
237
|
+
color: '#ef4444',
|
|
238
|
+
border: '1px solid rgba(239, 68, 68, 0.2)',
|
|
239
|
+
},
|
|
240
|
+
grid: {
|
|
241
|
+
display: 'flex',
|
|
242
|
+
flexDirection: 'column',
|
|
243
|
+
gap: '8px',
|
|
244
|
+
},
|
|
245
|
+
section: {
|
|
246
|
+
display: 'flex',
|
|
247
|
+
flexDirection: 'column',
|
|
248
|
+
gap: '4px',
|
|
249
|
+
},
|
|
250
|
+
sectionLabel: {
|
|
251
|
+
fontSize: '10px',
|
|
252
|
+
fontWeight: 600,
|
|
253
|
+
color: '#666',
|
|
254
|
+
textTransform: 'uppercase',
|
|
255
|
+
letterSpacing: '0.3px',
|
|
256
|
+
},
|
|
257
|
+
values: {
|
|
258
|
+
display: 'flex',
|
|
259
|
+
flexWrap: 'wrap',
|
|
260
|
+
gap: '8px',
|
|
261
|
+
},
|
|
262
|
+
value: {
|
|
263
|
+
display: 'flex',
|
|
264
|
+
gap: '4px',
|
|
265
|
+
fontSize: '11px',
|
|
266
|
+
},
|
|
267
|
+
valueLabel: {
|
|
268
|
+
color: '#888',
|
|
269
|
+
},
|
|
270
|
+
valueText: {
|
|
271
|
+
color: '#fff',
|
|
272
|
+
},
|
|
273
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulation Panel Component
|
|
3
|
+
*
|
|
4
|
+
* Panel for transaction simulation controls.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useHud } from '../providers/HudProvider'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for SimulationPanel
|
|
11
|
+
*/
|
|
12
|
+
export interface SimulationPanelProps {
|
|
13
|
+
/** Custom class name */
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Simulation panel for transaction dry-runs
|
|
19
|
+
*/
|
|
20
|
+
export function SimulationPanel({ className = '' }: SimulationPanelProps) {
|
|
21
|
+
const { simulationService, forkService, activePanel } = useHud()
|
|
22
|
+
|
|
23
|
+
// Don't render if not the active panel
|
|
24
|
+
if (activePanel !== 'simulation') return null
|
|
25
|
+
|
|
26
|
+
// Show message if simulation service not available
|
|
27
|
+
if (!simulationService) {
|
|
28
|
+
return (
|
|
29
|
+
<div className={className} style={{ color: '#666' }}>
|
|
30
|
+
<p>Simulation service not available.</p>
|
|
31
|
+
<p style={{ fontSize: '10px', marginTop: '8px' }}>
|
|
32
|
+
Install @sigil/simulation to enable transaction simulation.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const forkState = forkService?.getState()
|
|
39
|
+
const forkActive = forkState?.active ?? false
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={className}>
|
|
43
|
+
{/* Fork Status */}
|
|
44
|
+
<div style={{ marginBottom: '16px' }}>
|
|
45
|
+
<div
|
|
46
|
+
style={{
|
|
47
|
+
display: 'flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
gap: '8px',
|
|
50
|
+
marginBottom: '8px',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
style={{
|
|
55
|
+
width: '8px',
|
|
56
|
+
height: '8px',
|
|
57
|
+
borderRadius: '50%',
|
|
58
|
+
backgroundColor: forkActive ? '#10b981' : '#666',
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
<span style={{ color: forkActive ? '#10b981' : '#888' }}>
|
|
62
|
+
{forkActive ? 'Fork Active' : 'No Fork'}
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{forkState && forkActive && (
|
|
67
|
+
<div
|
|
68
|
+
style={{
|
|
69
|
+
padding: '8px',
|
|
70
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
71
|
+
borderRadius: '4px',
|
|
72
|
+
fontSize: '10px',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<div style={{ color: '#888', marginBottom: '4px' }}>
|
|
76
|
+
Chain: {forkState.chainId}
|
|
77
|
+
</div>
|
|
78
|
+
<div style={{ color: '#888', marginBottom: '4px' }}>
|
|
79
|
+
Block: {forkState.blockNumber?.toString()}
|
|
80
|
+
</div>
|
|
81
|
+
<div style={{ color: '#888' }}>
|
|
82
|
+
Snapshots: {forkState.snapshotCount}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Simulation Info */}
|
|
89
|
+
<div
|
|
90
|
+
style={{
|
|
91
|
+
padding: '12px',
|
|
92
|
+
backgroundColor: 'rgba(59, 130, 246, 0.05)',
|
|
93
|
+
border: '1px solid rgba(59, 130, 246, 0.2)',
|
|
94
|
+
borderRadius: '4px',
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<div style={{ color: '#3b82f6', fontSize: '11px', marginBottom: '8px' }}>
|
|
98
|
+
Transaction Simulation
|
|
99
|
+
</div>
|
|
100
|
+
<div style={{ color: '#888', fontSize: '10px', lineHeight: 1.6 }}>
|
|
101
|
+
Simulate transactions before sending them on-chain. View gas estimates,
|
|
102
|
+
balance changes, and potential revert reasons.
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
style={{
|
|
106
|
+
marginTop: '12px',
|
|
107
|
+
padding: '8px',
|
|
108
|
+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
109
|
+
borderRadius: '4px',
|
|
110
|
+
fontSize: '10px',
|
|
111
|
+
color: '#666',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
To simulate a transaction, use the simulation service programmatically:
|
|
115
|
+
<pre
|
|
116
|
+
style={{
|
|
117
|
+
marginTop: '8px',
|
|
118
|
+
fontFamily: 'ui-monospace, monospace',
|
|
119
|
+
color: '#10b981',
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{`simulationService.simulate({
|
|
123
|
+
from: '0x...',
|
|
124
|
+
to: '0x...',
|
|
125
|
+
value: 1000000n,
|
|
126
|
+
data: '0x...'
|
|
127
|
+
})`}
|
|
128
|
+
</pre>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Features List */}
|
|
133
|
+
<div style={{ marginTop: '16px' }}>
|
|
134
|
+
<label
|
|
135
|
+
style={{
|
|
136
|
+
display: 'block',
|
|
137
|
+
color: '#888',
|
|
138
|
+
fontSize: '10px',
|
|
139
|
+
marginBottom: '8px',
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
Simulation Features
|
|
143
|
+
</label>
|
|
144
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
145
|
+
{[
|
|
146
|
+
{ icon: '⛽', label: 'Gas estimation' },
|
|
147
|
+
{ icon: '💰', label: 'Balance changes' },
|
|
148
|
+
{ icon: '📝', label: 'State changes' },
|
|
149
|
+
{ icon: '❌', label: 'Revert reasons' },
|
|
150
|
+
{ icon: '📊', label: 'Event logs' },
|
|
151
|
+
].map((feature) => (
|
|
152
|
+
<div
|
|
153
|
+
key={feature.label}
|
|
154
|
+
style={{
|
|
155
|
+
display: 'flex',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
gap: '8px',
|
|
158
|
+
padding: '6px 8px',
|
|
159
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
160
|
+
borderRadius: '4px',
|
|
161
|
+
fontSize: '11px',
|
|
162
|
+
color: '#888',
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<span>{feature.icon}</span>
|
|
166
|
+
<span>{feature.label}</span>
|
|
167
|
+
</div>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Comparison Component
|
|
3
|
+
*
|
|
4
|
+
* Compare state between real and impersonated addresses.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useHud } from '../providers/HudProvider'
|
|
8
|
+
import { DEFAULT_LENS_STATE } from '../types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Props for StateComparison
|
|
12
|
+
*/
|
|
13
|
+
export interface StateComparisonProps {
|
|
14
|
+
/** Custom class name */
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* State comparison panel
|
|
20
|
+
*/
|
|
21
|
+
export function StateComparison({ className = '' }: StateComparisonProps) {
|
|
22
|
+
const { lensService, forkService, activePanel } = useHud()
|
|
23
|
+
|
|
24
|
+
// Don't render if not the active panel
|
|
25
|
+
if (activePanel !== 'state') return null
|
|
26
|
+
|
|
27
|
+
const lensState = lensService?.getState() ?? DEFAULT_LENS_STATE
|
|
28
|
+
const forkState = forkService?.getState()
|
|
29
|
+
const isImpersonating = lensState.enabled && lensState.impersonatedAddress !== null
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={className}>
|
|
33
|
+
{/* Addresses */}
|
|
34
|
+
<div style={{ marginBottom: '16px' }}>
|
|
35
|
+
<label
|
|
36
|
+
style={{
|
|
37
|
+
display: 'block',
|
|
38
|
+
color: '#888',
|
|
39
|
+
fontSize: '10px',
|
|
40
|
+
marginBottom: '8px',
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
Address Context
|
|
44
|
+
</label>
|
|
45
|
+
|
|
46
|
+
<div
|
|
47
|
+
style={{
|
|
48
|
+
display: 'flex',
|
|
49
|
+
flexDirection: 'column',
|
|
50
|
+
gap: '8px',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{/* Real Address */}
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
padding: '8px',
|
|
57
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
58
|
+
borderRadius: '4px',
|
|
59
|
+
border: !isImpersonating
|
|
60
|
+
? '1px solid rgba(16, 185, 129, 0.3)'
|
|
61
|
+
: '1px solid rgba(255, 255, 255, 0.05)',
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
<div
|
|
65
|
+
style={{
|
|
66
|
+
display: 'flex',
|
|
67
|
+
justifyContent: 'space-between',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
marginBottom: '4px',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<span style={{ color: '#888', fontSize: '10px' }}>Real</span>
|
|
73
|
+
{!isImpersonating && (
|
|
74
|
+
<span
|
|
75
|
+
style={{
|
|
76
|
+
fontSize: '9px',
|
|
77
|
+
padding: '2px 6px',
|
|
78
|
+
backgroundColor: 'rgba(16, 185, 129, 0.2)',
|
|
79
|
+
borderRadius: '4px',
|
|
80
|
+
color: '#10b981',
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
Active
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
<code
|
|
88
|
+
style={{
|
|
89
|
+
fontSize: '10px',
|
|
90
|
+
color: lensState.realAddress ? '#fff' : '#666',
|
|
91
|
+
wordBreak: 'break-all',
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{lensState.realAddress ?? 'Not connected'}
|
|
95
|
+
</code>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Impersonated Address */}
|
|
99
|
+
<div
|
|
100
|
+
style={{
|
|
101
|
+
padding: '8px',
|
|
102
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
103
|
+
borderRadius: '4px',
|
|
104
|
+
border: isImpersonating
|
|
105
|
+
? '1px solid rgba(16, 185, 129, 0.3)'
|
|
106
|
+
: '1px solid rgba(255, 255, 255, 0.05)',
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
display: 'flex',
|
|
112
|
+
justifyContent: 'space-between',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
marginBottom: '4px',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<span style={{ color: '#888', fontSize: '10px' }}>Impersonated</span>
|
|
118
|
+
{isImpersonating && (
|
|
119
|
+
<span
|
|
120
|
+
style={{
|
|
121
|
+
fontSize: '9px',
|
|
122
|
+
padding: '2px 6px',
|
|
123
|
+
backgroundColor: 'rgba(16, 185, 129, 0.2)',
|
|
124
|
+
borderRadius: '4px',
|
|
125
|
+
color: '#10b981',
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
Active
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
<code
|
|
133
|
+
style={{
|
|
134
|
+
fontSize: '10px',
|
|
135
|
+
color: lensState.impersonatedAddress ? '#fff' : '#666',
|
|
136
|
+
wordBreak: 'break-all',
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{lensState.impersonatedAddress ?? 'None'}
|
|
140
|
+
</code>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{/* Fork State */}
|
|
146
|
+
{forkState && (
|
|
147
|
+
<div style={{ marginBottom: '16px' }}>
|
|
148
|
+
<label
|
|
149
|
+
style={{
|
|
150
|
+
display: 'block',
|
|
151
|
+
color: '#888',
|
|
152
|
+
fontSize: '10px',
|
|
153
|
+
marginBottom: '8px',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
Fork State
|
|
157
|
+
</label>
|
|
158
|
+
|
|
159
|
+
<div
|
|
160
|
+
style={{
|
|
161
|
+
padding: '8px',
|
|
162
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
163
|
+
borderRadius: '4px',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<div
|
|
167
|
+
style={{
|
|
168
|
+
display: 'grid',
|
|
169
|
+
gridTemplateColumns: '1fr 1fr',
|
|
170
|
+
gap: '8px',
|
|
171
|
+
fontSize: '10px',
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<div>
|
|
175
|
+
<span style={{ color: '#666' }}>Status: </span>
|
|
176
|
+
<span style={{ color: forkState.active ? '#10b981' : '#888' }}>
|
|
177
|
+
{forkState.active ? 'Active' : 'Inactive'}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
<div>
|
|
181
|
+
<span style={{ color: '#666' }}>Chain: </span>
|
|
182
|
+
<span style={{ color: '#fff' }}>
|
|
183
|
+
{forkState.chainId ?? '—'}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
<span style={{ color: '#666' }}>Block: </span>
|
|
188
|
+
<span style={{ color: '#fff' }}>
|
|
189
|
+
{forkState.blockNumber?.toString() ?? '—'}
|
|
190
|
+
</span>
|
|
191
|
+
</div>
|
|
192
|
+
<div>
|
|
193
|
+
<span style={{ color: '#666' }}>Snapshots: </span>
|
|
194
|
+
<span style={{ color: '#fff' }}>{forkState.snapshotCount}</span>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{forkState.rpcUrl && (
|
|
199
|
+
<div style={{ marginTop: '8px' }}>
|
|
200
|
+
<span style={{ color: '#666', fontSize: '10px' }}>RPC: </span>
|
|
201
|
+
<code
|
|
202
|
+
style={{
|
|
203
|
+
fontSize: '9px',
|
|
204
|
+
color: '#888',
|
|
205
|
+
wordBreak: 'break-all',
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{forkState.rpcUrl}
|
|
209
|
+
</code>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{/* State Comparison Info */}
|
|
217
|
+
<div
|
|
218
|
+
style={{
|
|
219
|
+
padding: '12px',
|
|
220
|
+
backgroundColor: 'rgba(251, 191, 36, 0.05)',
|
|
221
|
+
border: '1px solid rgba(251, 191, 36, 0.2)',
|
|
222
|
+
borderRadius: '4px',
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
<div
|
|
226
|
+
style={{ color: '#fbbf24', fontSize: '11px', marginBottom: '8px' }}
|
|
227
|
+
>
|
|
228
|
+
State Comparison
|
|
229
|
+
</div>
|
|
230
|
+
<div style={{ color: '#888', fontSize: '10px', lineHeight: 1.6 }}>
|
|
231
|
+
When impersonating, reads use the impersonated address while writes
|
|
232
|
+
still use your real wallet. This lets you test how the UI looks for
|
|
233
|
+
different users without affecting their funds.
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
)
|
|
238
|
+
}
|